mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-16 13:08:06 +01:00
Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64897a39bf | ||
|
|
dfda33c1aa | ||
|
|
d46eafb248 | ||
|
|
ee6f0d0c49 | ||
|
|
b7b86bcc44 | ||
|
|
bbf3424933 | ||
|
|
2fc938575d | ||
|
|
ff9d51863e | ||
|
|
adb0a0fbe4 | ||
|
|
a071e4b875 | ||
|
|
a74de152d7 | ||
|
|
109ec52d50 | ||
|
|
874447cb41 | ||
|
|
8b7a013319 | ||
|
|
3e647e4af1 | ||
|
|
0da85e1463 | ||
|
|
dcf6e63471 | ||
|
|
cbb2f28c3f | ||
|
|
be734fc026 | ||
|
|
b306138574 | ||
|
|
1ebf456ffc | ||
|
|
8257a11dcb | ||
|
|
6887f732ee | ||
|
|
d088d8a7b8 | ||
|
|
f60543a234 | ||
|
|
2531c8e66d | ||
|
|
4b68760f6a | ||
|
|
568772382f | ||
|
|
46879dc1b7 | ||
|
|
a94782d94b | ||
|
|
853d58ad5f | ||
|
|
38b1eb6c5f | ||
|
|
f24ff9c47f | ||
|
|
60210aad75 | ||
|
|
67e85f98e2 | ||
|
|
b3a52482f2 | ||
|
|
86dc49ecc9 | ||
|
|
c937736734 | ||
|
|
d379c579c0 | ||
|
|
f983c974c4 | ||
|
|
b90b151588 | ||
|
|
34d2f57801 | ||
|
|
2c98628f98 | ||
|
|
681f0e5684 | ||
|
|
e40491208a | ||
|
|
00594ea59b | ||
|
|
3ba95d3c4d | ||
|
|
3424ce118d | ||
|
|
40b1d30f5c | ||
|
|
8ec23c042d | ||
|
|
81463cd21d | ||
|
|
c44d363f62 | ||
|
|
fbfa14a6a3 | ||
|
|
4127caac76 | ||
|
|
d6476d17f9 | ||
|
|
5fc44b97c6 | ||
|
|
15e418e6c6 | ||
|
|
3b8ca9886d | ||
|
|
4c5833083f | ||
|
|
83d609d530 | ||
|
|
1b34df15ac | ||
|
|
0178ca9586 | ||
|
|
40ecb23d9a | ||
|
|
85734b8615 | ||
|
|
ab26e4ba7d | ||
|
|
edbbb33f69 | ||
|
|
3de3aa006c | ||
|
|
784f1f51dd | ||
|
|
0787ec2d12 | ||
|
|
a8f643939e | ||
|
|
6f77ee80ce | ||
|
|
e2d4ba529d | ||
|
|
1c707ca00d | ||
|
|
00e951f708 | ||
|
|
0544a01c5b | ||
|
|
290ab1d9c5 | ||
|
|
254c4ed7d3 | ||
|
|
a603ea56c1 | ||
|
|
a90e95f7d1 | ||
|
|
bc2315b7d9 | ||
|
|
87fd85ec3f | ||
|
|
3fef86834f | ||
|
|
b5e8685a2c | ||
|
|
15ee768729 | ||
|
|
8955595dc6 | ||
|
|
fd6bcd3f84 | ||
|
|
9d23b82d1d | ||
|
|
511ed6a86c | ||
|
|
e8daf7f810 | ||
|
|
a43c68c501 | ||
|
|
ef7d3ce549 | ||
|
|
c2e561cfe4 | ||
|
|
1d1c36b44c | ||
|
|
95abc759b9 | ||
|
|
700b2bb4d7 | ||
|
|
1d077c45d5 | ||
|
|
14cca48e96 | ||
|
|
22430e168a | ||
|
|
c1e0654417 | ||
|
|
1a7eb27cad | ||
|
|
0d5f008168 | ||
|
|
ba2716a66a | ||
|
|
5d66155885 | ||
|
|
02f3164af3 | ||
|
|
240db8ee19 | ||
|
|
b905216a95 | ||
|
|
c47d928f49 | ||
|
|
7e0a655c64 | ||
|
|
02bbc9b9cf | ||
|
|
98e1d1b90e | ||
|
|
2b1e7bcc57 |
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,31 +0,0 @@
|
||||
---
|
||||
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
|
||||
@nuxt/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/nuxt-ui
|
||||
-->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
|
||||
### What is Expected?
|
||||
|
||||
|
||||
### What is actually happening?
|
||||
60
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: "🐛 Bug report"
|
||||
description: Report a bug to help us improve the module.
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before reporting a bug, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: textarea
|
||||
id: env
|
||||
attributes:
|
||||
label: Environment
|
||||
description: You can use `npx nuxi info` to fill this section
|
||||
placeholder: |
|
||||
- Operating System: `Darwin`
|
||||
- Node Version: `v18.16.0`
|
||||
- Nuxt Version: `3.7.3`
|
||||
- CLI Version: `3.8.4`
|
||||
- Nitro Version: `2.6.3`
|
||||
- Package Manager: `pnpm@8.7.4`
|
||||
- Builder: `-`
|
||||
- User Config: `-`
|
||||
- Runtime Modules: `-`
|
||||
- Build Modules: `-`
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
placeholder: v2.8.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Please provide a reproduction link using this template https://stackblitz.com/edit/nuxt-ui. A minimal [reproduction is required](https://antfu.me/posts/why-reproductions-are-required) unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided we might close it.
|
||||
placeholder: https://stackblitz.com/edit/nuxt-ui
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additonal
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: If applicable, add any other context or screenshots here.
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |
|
||||
Optional if provided reproduction. Please try not to insert an image but copy paste the log text.
|
||||
render: shell-script
|
||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Nuxt Community Discord
|
||||
url: https://discord.nuxtjs.org/
|
||||
about: Consider asking questions about the module here.
|
||||
- name: 📖 Documentation
|
||||
url: https://ui.nuxt.com
|
||||
about: Check the documentation for guides and examples.
|
||||
- name: 📚 Discord
|
||||
url: https://discord.com/channels/473401852243869706/1153996761426300948
|
||||
about: Consider asking questions in the `#ui` channel.
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
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. -->
|
||||
20
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: "🚀 Feature request"
|
||||
description: Suggest an idea or enhancement for the module.
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before requesting a feature, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of what you think would be an helpful addition to the module, including the possible use cases and alternatives you have considered. If you have a working prototype or module that implements it, please include a link.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additonal
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: If applicable, add any other context or screenshots here.
|
||||
16
.github/ISSUE_TEMPLATE/question.md
vendored
16
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,16 +0,0 @@
|
||||
---
|
||||
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/
|
||||
-->
|
||||
14
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: "💬 Question"
|
||||
description: Ask a question about the module.
|
||||
labels: ["question"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before asking a question, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
||||
33
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
33
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<!---
|
||||
☝️ PR title should follow conventional commits (https://conventionalcommits.org)
|
||||
-->
|
||||
|
||||
### 🔗 Linked issue
|
||||
|
||||
<!-- Please ensure there is an open issue and mention its number as #123 -->
|
||||
|
||||
### ❓ Type of change
|
||||
|
||||
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->
|
||||
|
||||
- [ ] 📖 Documentation (updates to the documentation or readme)
|
||||
- [ ] 🐞 Bug fix (a non-breaking change that fixes an issue)
|
||||
- [ ] 👌 Enhancement (improving an existing functionality)
|
||||
- [ ] ✨ New feature (a non-breaking change that adds functionality)
|
||||
- [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries)
|
||||
- [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
### 📚 Description
|
||||
|
||||
<!-- Describe your changes in detail -->
|
||||
<!-- Why is this change required? What problem does it solve? -->
|
||||
<!-- If it resolves an open issue, please link to the issue here. For example "Resolves #1337" -->
|
||||
|
||||
### 📝 Checklist
|
||||
|
||||
<!-- Put an `x` in all the boxes that apply. -->
|
||||
<!-- If your change requires a documentation PR, please link it appropriately -->
|
||||
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] I have linked an issue or discussion.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
9
.github/workflows/ci-dev.yml
vendored
9
.github/workflows/ci-dev.yml
vendored
@@ -49,15 +49,18 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Prepare
|
||||
run: pnpm run dev:prepare
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Release Edge
|
||||
if: github.event_name == 'push'
|
||||
run: ./scripts/release-edge.sh
|
||||
|
||||
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -49,15 +49,18 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Prepare
|
||||
run: pnpm run dev:prepare
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Version Check
|
||||
id: check
|
||||
uses: EndBug/version-check@v2
|
||||
|
||||
3
.nuxtrc
Normal file
3
.nuxtrc
Normal file
@@ -0,0 +1,3 @@
|
||||
imports.autoImport=false
|
||||
typescript.includeWorkspace=true
|
||||
typescript.strict=false
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## [2.9.0](https://github.com/nuxt/ui/compare/v2.8.1...v2.9.0) (2023-10-02)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **module:** use `tailwind-merge` for `app.config` & move config to components & type props (#692)
|
||||
|
||||
### Features
|
||||
|
||||
* **FormGroup:** add slots ([#714](https://github.com/nuxt/ui/issues/714)) ([2fc9385](https://github.com/nuxt/ui/commit/2fc938575d2e409ba9df9fb2ddb8d51d021a1756))
|
||||
* **Link:** add `active` prop to override default behaviour ([#732](https://github.com/nuxt/ui/issues/732)) ([8257a11](https://github.com/nuxt/ui/commit/8257a11dcba9c34053f8061ed1383894d06b2a6c))
|
||||
* **Link:** add `as` prop ([#535](https://github.com/nuxt/ui/issues/535)) ([e404912](https://github.com/nuxt/ui/commit/e40491208ac1096e505803072df0d9e2e771008e))
|
||||
* **module:** use `tailwind-merge` for `app.config` & move config to components & type props ([#692](https://github.com/nuxt/ui/issues/692)) ([34d2f57](https://github.com/nuxt/ui/commit/34d2f57801d08d26262fdff4398ec3d3329b4bb0))
|
||||
* remove `lodash-es` ([#648](https://github.com/nuxt/ui/issues/648)) ([d6476d1](https://github.com/nuxt/ui/commit/d6476d17f9b17317a7160271dacdb854f30237ae))
|
||||
* **Table:** add ability to custom style for `td` and `tr` ([#741](https://github.com/nuxt/ui/issues/741)) ([874447c](https://github.com/nuxt/ui/commit/874447cb41a77868513459eee5d3301fe8b8e9a1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Accordion:** close other items in circular order ([#735](https://github.com/nuxt/ui/issues/735)) ([6887f73](https://github.com/nuxt/ui/commit/6887f732ee8e14625459a0576460523845cb0a6d))
|
||||
* **FormGroup:** prevent input click from propagating to label ([#651](https://github.com/nuxt/ui/issues/651)) ([4c58330](https://github.com/nuxt/ui/commit/4c5833083f0840add52f3c67efc42b8db5687d37))
|
||||
* **FormGroup:** use explicit label instead of implicit label ([#638](https://github.com/nuxt/ui/issues/638)) ([681f0e5](https://github.com/nuxt/ui/commit/681f0e5684feaad0c711130404751f2fd65ddbe4))
|
||||
* **module:** move `@headlessui/tailwindcss` to plugins on module install ([3e647e4](https://github.com/nuxt/ui/commit/3e647e4af154dad7fa186f062ce984e4d8d0e202))
|
||||
* **module:** retain props reactivity through `useUI` ([#745](https://github.com/nuxt/ui/issues/745)) ([109ec52](https://github.com/nuxt/ui/commit/109ec52d50b0b32b0f0b24ece5b92cd7bbce29da))
|
||||
* **Pagination:** handle `max > 5` and `max` equal total pages ([#728](https://github.com/nuxt/ui/issues/728)) ([a071e4b](https://github.com/nuxt/ui/commit/a071e4b8755f5dbbdfd05985c8fcb65c3cdab3ec))
|
||||
* **Range:** fix track pseudo-elements for mozilla ([#636](https://github.com/nuxt/ui/issues/636)) ([8955595](https://github.com/nuxt/ui/commit/8955595dc6904d0090ad7f82ed8b376a15e51f94))
|
||||
* **SelectMenu:** handle numbers ([0544a01](https://github.com/nuxt/ui/commit/0544a01c5b7ae534a595e6c91d2884a601ae3185)), closes [#574](https://github.com/nuxt/ui/issues/574)
|
||||
* **Table:** add missing classes in `app.config.ts` ([a603ea5](https://github.com/nuxt/ui/commit/a603ea56c165e9ad01482d092460da3991f3e41d)), closes [#655](https://github.com/nuxt/ui/issues/655)
|
||||
* **Table:** select all rows without select listener ([#652](https://github.com/nuxt/ui/issues/652)) ([83d609d](https://github.com/nuxt/ui/commit/83d609d53067b2639a55a0e367a5e7adbd8a22fc))
|
||||
* **Tabs:** add visible focus indicator on active tabs ([#690](https://github.com/nuxt/ui/issues/690)) ([be734fc](https://github.com/nuxt/ui/commit/be734fc026b75bc8c921e9401ba6e97f65356cec))
|
||||
* **Tabs:** allow custom keys in `TabItem` ([#671](https://github.com/nuxt/ui/issues/671)) ([15e418e](https://github.com/nuxt/ui/commit/15e418e6c6f981afd2c0e8f27dedb303b8cbad70))
|
||||
* **Tabs:** prevent focus of `TabPanel` with `tabindex="-1"` ([cbb2f28](https://github.com/nuxt/ui/commit/cbb2f28c3fd96e45c7af20675b5b67576ddc0d63))
|
||||
|
||||
## [2.8.1](https://github.com/nuxt/ui/compare/v2.8.0...v2.8.1) (2023-09-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Form:** fix `getValibotError` to avoid importing `safeParseAsync` ([#640](https://github.com/nuxt/ui/issues/640)) ([e8daf7f](https://github.com/nuxt/ui/commit/e8daf7f81018c01c28c2c38aed6ee57ef887f823))
|
||||
* **Form:** fix valibot imports ([#617](https://github.com/nuxt/ui/issues/617)) ([1a7eb27](https://github.com/nuxt/ui/commit/1a7eb27cad9f3357c4dcde188530cdb0001d3ae6))
|
||||
* **Pagination:** page numbers not clickable ([#624](https://github.com/nuxt/ui/issues/624)) ([c1e0654](https://github.com/nuxt/ui/commit/c1e0654417ad39df8be3f2172ab4e0af6dacb631))
|
||||
|
||||
## [2.8.0](https://github.com/nuxt/ui/compare/v2.7.0...v2.8.0) (2023-09-07)
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ Is has been developed by [NuxtLabs](https://nuxtlabs.com/) for [Volta](https://v
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
||||
|
||||
Read more on [ui.nuxt.com](https://ui.nuxt.com)
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# To use Nuxt Elements in production
|
||||
NUXT_ELEMENTS_TOKEN=
|
||||
# To link Nuxt UI Pro in development
|
||||
NUXT_UI_PRO_PATH=
|
||||
# To use Nuxt UI Pro in production
|
||||
NUXT_UI_PRO_TOKEN=
|
||||
# Used when pre-rendering the docs for dynamic OG images
|
||||
NUXT_PUBLIC_SITE_URL=
|
||||
|
||||
1
docs/.nuxtrc
Normal file
1
docs/.nuxtrc
Normal file
@@ -0,0 +1 @@
|
||||
imports.autoImport=true
|
||||
45
docs/app.vue
45
docs/app.vue
@@ -10,7 +10,7 @@
|
||||
<Footer />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUDocsSearch :files="files" :navigation="navigation" />
|
||||
<LazyUDocsSearch ref="searchRef" :files="files" :navigation="navigation" :groups="groups" />
|
||||
</ClientOnly>
|
||||
|
||||
<UNotifications>
|
||||
@@ -27,36 +27,52 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { debounce } from 'perfect-debounce'
|
||||
import type { ParsedContent } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const searchRef = ref()
|
||||
|
||||
const route = useRoute()
|
||||
const colorMode = useColorMode()
|
||||
const { prefix, removePrefixFromNavigation, removePrefixFromFiles } = useContentSource()
|
||||
const { branch, branches } = useContentSource()
|
||||
|
||||
const { data: nav } = await useAsyncData('navigation', () => fetchContentNavigation())
|
||||
|
||||
const { data: search } = useLazyFetch('/api/search.json', {
|
||||
default: () => [],
|
||||
server: false
|
||||
})
|
||||
const { data: files } = useLazyFetch<ParsedContent[]>('/api/search.json', { default: () => [], server: false })
|
||||
|
||||
// Computed
|
||||
const navigation = computed(() => {
|
||||
const navigation = nav.value.find(link => link._path === prefix.value)?.children || []
|
||||
|
||||
return prefix.value === '/main' ? removePrefixFromNavigation(navigation) : navigation
|
||||
const navigation = computed(() => {
|
||||
const main = nav.value.filter(item => item._path !== '/dev')
|
||||
const dev = nav.value.find(item => item._path === '/dev')?.children
|
||||
|
||||
return branch.value?.name === 'dev' ? dev : main
|
||||
})
|
||||
|
||||
const files = computed(() => {
|
||||
const files = search.value.filter(file => file._path.startsWith(prefix.value))
|
||||
const groups = computed(() => {
|
||||
if (route.path === '/') {
|
||||
return []
|
||||
}
|
||||
|
||||
return prefix.value === '/main' ? removePrefixFromFiles(files) : files
|
||||
return [{ key: 'branch', label: 'Branch', commands: branches.value }]
|
||||
})
|
||||
|
||||
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
|
||||
|
||||
// Watch
|
||||
|
||||
watch(() => searchRef.value?.commandPaletteRef?.query, debounce((query: string) => {
|
||||
if (!query) {
|
||||
return
|
||||
}
|
||||
|
||||
useTrackEvent('Search', { props: { query: `${query} - ${searchRef.value?.commandPaletteRef.results.length} results` } })
|
||||
}, 500))
|
||||
|
||||
// Head
|
||||
|
||||
useHead({
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [
|
||||
@@ -74,6 +90,7 @@ useServerSeoMeta({
|
||||
})
|
||||
|
||||
// Provide
|
||||
|
||||
provide('navigation', navigation)
|
||||
provide('files', files)
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
color="gray"
|
||||
:ui="{ icon: { trailing: { padding: { sm: 'pe-1.5' } } } }"
|
||||
:ui-menu="{ option: { container: 'gap-1.5' } }"
|
||||
@update:model-value="selectBranch"
|
||||
@update:model-value="select"
|
||||
>
|
||||
<template #label>
|
||||
<UIcon v-if="branch.icon" :name="branch.icon" class="w-4 h-4 flex-shrink-0 text-gray-600 dark:text-gray-300" />
|
||||
@@ -32,19 +32,5 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { branches, branch } = useContentSource()
|
||||
|
||||
function selectBranch (branch) {
|
||||
if (branch.name === 'dev') {
|
||||
if (route.path.startsWith('/dev')) {
|
||||
return
|
||||
}
|
||||
|
||||
router.push(`/dev${route.path}`)
|
||||
} else {
|
||||
router.push(route.path.replace('/dev', ''))
|
||||
}
|
||||
}
|
||||
const { branches, branch, select } = useContentSource()
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="w-full h-px bg-gray-200 dark:bg-gray-800 flex items-center justify-center">
|
||||
<div v-if="$route.path !== '/playground'" class="w-full h-px bg-gray-200 dark:bg-gray-800 flex items-center justify-center">
|
||||
<div class="bg-white dark:bg-gray-900 px-4">
|
||||
<LogoOnly class="w-5 h-5" />
|
||||
</div>
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<UFooter :links="[]" :ui="{ bottom: { container: 'lg:py-4' } }">
|
||||
<template #left>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-300">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs" class="inline-block">
|
||||
<LogoLabs class="text-gray-900 dark:text-white h-4 w-auto" />
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<template #center>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-300">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Published under <NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="text-gray-900 dark:text-white">
|
||||
MIT License
|
||||
</NuxtLink>
|
||||
@@ -24,9 +24,14 @@
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<USocialButton aria-label="Nuxt Website" icon="i-simple-icons-nuxtdotjs" to="https://nuxt.com" />
|
||||
<USocialButton aria-label="Nuxt on X" icon="i-simple-icons-x" to="https://x.com/nuxtlabs" />
|
||||
<USocialButton aria-label="Nuxt UI on GitHub" icon="i-simple-icons-github" to="https://github.com/nuxt/ui" />
|
||||
<UButton aria-label="Nuxt Website" icon="i-simple-icons-nuxtdotjs" to="https://nuxt.com" target="_blank" v-bind="($ui.button.secondary as any)" />
|
||||
<UButton aria-label="Nuxt UI on Discord" icon="i-simple-icons-discord" to="https://discord.com/invite/ps2h6QT" target="_blank" v-bind="($ui.button.secondary as any)" />
|
||||
<UButton aria-label="Nuxt on X" icon="i-simple-icons-x" to="https://x.com/nuxt_js" target="_blank" v-bind="($ui.button.secondary as any)" />
|
||||
<UButton aria-label="Nuxt UI on GitHub" icon="i-simple-icons-github" to="https://github.com/nuxt/ui" target="_blank" v-bind="($ui.button.secondary as any)" />
|
||||
</template>
|
||||
</UFooter>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// force typescript
|
||||
</script>
|
||||
|
||||
@@ -7,23 +7,28 @@
|
||||
}"
|
||||
>
|
||||
<template #left>
|
||||
<NuxtLink to="/" class="flex items-end gap-1.5 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" aria-label="Nuxt UI">
|
||||
<Logo class="w-auto h-6" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<template v-if="$route.path !== '/'" #center>
|
||||
<UDocsSearchButton class="ml-1.5 flg:w-64 xl:w-96" />
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<ColorPicker />
|
||||
|
||||
<UDocsSearchButton v-if="$route.path === '/'" icon-only />
|
||||
<UTooltip text="Search" :shortcuts="[metaSymbol, 'K']">
|
||||
<UDocsSearchButton :label="null" />
|
||||
</UTooltip>
|
||||
|
||||
<UColorModeButton v-if="!$colorMode.forced" />
|
||||
<UColorModeButton />
|
||||
|
||||
<USocialButton to="https://github.com/nuxt/ui" target="_blank" icon="i-simple-icons-github" class="hidden lg:inline-flex" />
|
||||
<UButton
|
||||
to="https://github.com/nuxt/ui"
|
||||
target="_blank"
|
||||
icon="i-simple-icons-github"
|
||||
aria-label="GitHub"
|
||||
class="hidden lg:inline-flex"
|
||||
v-bind="($ui.button.secondary as any)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
@@ -37,25 +42,23 @@
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const route = useRoute()
|
||||
const { mapContentNavigation } = useElementsHelpers()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
|
||||
const navigation = inject<Ref<NavItem[]>>('navigation')
|
||||
|
||||
const links = computed(() => {
|
||||
if (route.path !== '/') {
|
||||
return []
|
||||
}
|
||||
|
||||
return [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open-solid',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Examples',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
to: '/getting-started/examples'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
icon: 'i-simple-icons-stackblitz',
|
||||
to: 'https://stackblitz.com/edit/nuxt-ui?file=app.config.ts,app.vue',
|
||||
target: '_blank'
|
||||
to: '/playground'
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-heroicons-rocket-launch-solid',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<UPopover mode="hover">
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" aria-label="Color picker">
|
||||
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
</UButton>
|
||||
</template>
|
||||
@@ -30,7 +30,7 @@ const colorMode = useColorMode()
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryColors = computed(() => useWithout(appConfig.ui.colors, 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primaryColors = computed(() => appConfig.ui.colors.filter(color => color !== 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get () {
|
||||
return primaryColors.value.find(option => option.value === appConfig.ui.primary)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<UTooltip :text="color.value" class="capitalize" :open-delay="500">
|
||||
<UButton
|
||||
color="transparent"
|
||||
color="white"
|
||||
square
|
||||
:ui="{
|
||||
color: {
|
||||
transparent: {
|
||||
solid: 'bg-gray-100 dark:bg-gray-800',
|
||||
white: {
|
||||
solid: 'ring-0 bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-800',
|
||||
ghost: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,13 +46,17 @@
|
||||
</component>
|
||||
</div>
|
||||
|
||||
<ContentRenderer v-if="!previewOnly" :value="ast" class="[&>div>pre]:!rounded-t-none" />
|
||||
<ContentRenderer v-if="!previewOnly" :value="ast" class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
// @ts-ignore
|
||||
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
|
||||
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||
import * as config from '#ui/ui.config'
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const props = defineProps({
|
||||
@@ -114,15 +118,14 @@ const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
const camelName = camelCase(slug)
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
|
||||
// Computed
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const ui = computed(() => ({ ...appConfig.ui[camelName], ...props.ui }))
|
||||
const ui = computed(() => ({ ...config[camelName], ...props.ui }))
|
||||
|
||||
const fullProps = computed(() => ({ ...baseProps, ...componentProps }))
|
||||
const vModel = computed({
|
||||
@@ -138,8 +141,8 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
}
|
||||
|
||||
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
|
||||
const dottedKey = useKebabCase(key).replaceAll('-', '.')
|
||||
const keys = useGet(ui.value, dottedKey, {})
|
||||
const dottedKey = kebabCase(key).replaceAll('-', '.')
|
||||
const keys = ui.value[dottedKey] ?? {}
|
||||
let options = typeof keys === 'object' && Object.keys(keys)
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
// @ts-ignore
|
||||
@@ -149,7 +152,7 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
return {
|
||||
type: prop?.type || 'string',
|
||||
name: key,
|
||||
label: key === 'modelValue' ? 'value' : useCamelCase(key),
|
||||
label: key === 'modelValue' ? 'value' : camelCase(key),
|
||||
options
|
||||
}
|
||||
}).filter(Boolean))
|
||||
@@ -163,7 +166,7 @@ const code = computed(() => {
|
||||
continue
|
||||
}
|
||||
|
||||
code += ` ${(typeof value === 'boolean' && value !== true) || typeof value === 'object' || typeof value === 'number' ? ':' : ''}${key === 'modelValue' ? 'value' : useKebabCase(key)}${typeof value === 'boolean' && !!value && key !== 'modelValue' ? '' : `="${typeof value === 'object' ? renderObject(value) : value}"`}`
|
||||
code += ` ${(typeof value === 'boolean' && value !== true) || typeof value === 'object' || typeof value === 'number' ? ':' : ''}${key === 'modelValue' ? 'value' : kebabCase(key)}${typeof value === 'boolean' && !!value && key !== 'modelValue' ? '' : `="${typeof value === 'object' ? renderObject(value) : value}"`}`
|
||||
}
|
||||
|
||||
if (props.slots) {
|
||||
@@ -205,12 +208,17 @@ function renderObject (obj: any) {
|
||||
return obj
|
||||
}
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify(props)}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
const shikiHighlighter = useShikiHighlighter({})
|
||||
const codeHighlighter = async (code: string, lang: string, theme: any, highlights: number[]) => shikiHighlighter.getHighlightedAST(code, lang, theme, { highlights })
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify({ props: componentProps, slots: props.slots })}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}
|
||||
}), { watch: [code] })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="[&>div>pre]:!rounded-t-none">
|
||||
<div class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 relative not-prose rounded-t-md" :class="[{ 'p-4': padding, 'rounded-b-md': !$slots.code, 'border-b-0': !!$slots.code }, backgroundClass, overflowClass]">
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<script setup lang="ts">
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import * as config from '#ui/ui.config'
|
||||
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
@@ -13,17 +15,16 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
const camelName = camelCase(slug)
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const preset = appConfig.ui[camelName]
|
||||
const preset = config[camelName]
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-preset`, () => transformContent('content:_markdown.md', `
|
||||
\`\`\`json [appConfig.ui.${camelName}]
|
||||
\`\`\`json
|
||||
${JSON.stringify(preset, null, 2)}
|
||||
\`\`\`\
|
||||
`, {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
@@ -17,8 +19,8 @@ const props = defineProps({
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
const camelName = camelCase(slug)
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
</script>
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
<ComponentPropsField v-for="subProp in Object.values(prop.schema.schema)" :key="(subProp as any).name" :prop="subProp" />
|
||||
</FieldGroup>
|
||||
</Collapsible>
|
||||
<div v-else-if="prop.schema?.kind === 'enum' && prop.schema.type !== 'boolean' && startsWithCapital(prop.schema.type)" class="space-x-1 leading-7 -my-1">
|
||||
<code v-for="value in prop.schema.schema" :key="value">{{ value }}</code>
|
||||
<div v-else-if="prop.schema?.kind === 'enum' && prop.schema.type !== 'boolean' && startsWithCapital(prop.schema.type) && !prop.schema.type.startsWith(prop.schema.schema[0])" class="space-x-1 leading-7 -my-1">
|
||||
<code v-for="value in prop.schema.schema.filter(value => typeof value === 'string')" :key="value" class="whitespace-pre-wrap break-words">{{ value }}</code>
|
||||
</div>
|
||||
</Field>
|
||||
</template>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
@@ -28,8 +30,8 @@ const props = defineProps({
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
const camelName = camelCase(slug)
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
</script>
|
||||
|
||||
@@ -61,9 +61,9 @@ const items = [{
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<code>$ npm install @nuxt/ui</code>
|
||||
<code>$ nnpm install -D @nuxt/ui</code>
|
||||
<code>$ pnpm i -D @nuxt/ui</code>
|
||||
<code>$ npm i @nuxt/ui</code>
|
||||
<code>$ yarn add @nuxt/ui</code>
|
||||
<code>$ pnpm add @nuxt/ui</code>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
|
||||
@@ -5,9 +5,9 @@ const toast = useToast()
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const users = [
|
||||
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/benjamincanac', srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/benjamincanac 2x' } },
|
||||
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/Atinux', srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/Atinux 2x' } },
|
||||
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/smarroufin', srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/smarroufin 2x' } }
|
||||
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/benjamincanac', srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/benjamincanac 2x', loading: 'lazy' } },
|
||||
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/Atinux', srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/Atinux 2x', loading: 'lazy' } },
|
||||
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/smarroufin', srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/smarroufin 2x', loading: 'lazy' } }
|
||||
]
|
||||
|
||||
const actions = [
|
||||
|
||||
@@ -10,7 +10,7 @@ const label = computed(() => date.value.toLocaleDateString('en-us', { weekday: '
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="label" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker v-model="date" @close="close" />
|
||||
<LazyDatePicker v-model="date" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
|
||||
@@ -80,7 +80,7 @@ async function submit (event: FormSubmitEvent<Schema>) {
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="checkbox" label="Checkbox">
|
||||
<UCheckbox v-model="state.checkbox" />
|
||||
<UCheckbox v-model="state.checkbox" label="Check me" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="radio" label="Radio">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { string, object, email, minLength, Input } from 'valibot'
|
||||
import { string, objectAsync, email, minLength, Input } from 'valibot'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = object({
|
||||
const schema = objectAsync({
|
||||
email: string([email('Invalid email')]),
|
||||
password: string([minLength(8, 'Must be at least 8 characters')])
|
||||
})
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<UFormGroup label="Email" :error="!email && 'You must enter an email'" help="This is a nice email!">
|
||||
<template #default="{ error }">
|
||||
<UInput v-model="email" type="email" placeholder="Enter email" :trailing-icon="error ? 'i-heroicons-exclamation-triangle-20-solid' : undefined" />
|
||||
</template>
|
||||
|
||||
<template #error="{ error }">
|
||||
<UAlert v-if="error" icon="i-heroicons-exclamation-triangle-20-solid" :title="error" color="red" />
|
||||
<UAlert v-else icon="i-heroicons-check-circle-20-solid" title="Your email is valid" color="green" />
|
||||
</template>
|
||||
</UFormGroup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const email = ref('')
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@ const items = ref(Array(55))
|
||||
|
||||
<template>
|
||||
<div class="w-full divide-y divide-gray-200 dark:divide-gray-700 space-y-4">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="flex flex-wrap gap-1 justify-between w-full">
|
||||
<div dir="ltr">
|
||||
<UInput
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
@@ -27,7 +27,7 @@ const items = ref(Array(55))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between w-full pt-4">
|
||||
<div class="flex flex-wrap gap-1 justify-between w-full pt-4">
|
||||
<div dir="ltr">
|
||||
<UPagination
|
||||
v-model="page"
|
||||
|
||||
@@ -3,5 +3,6 @@ const value = ref(50)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URange v-model="value" />
|
||||
<label for="range" class="sr-only" />
|
||||
<URange id="range" v-model="value" name="range" />
|
||||
</template>
|
||||
|
||||
@@ -6,7 +6,7 @@ const selected = ref(people[3])
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton color="gray">
|
||||
<UButton color="gray" class="flex-1 justify-between">
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="[open && 'transform rotate-90']" />
|
||||
|
||||
@@ -143,7 +143,7 @@ const { data: todos, pending } = await useLazyAsyncData('todos', () => $fetch<{
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-down"
|
||||
trailing
|
||||
variant="soft"
|
||||
color="gray"
|
||||
size="xs"
|
||||
>
|
||||
Mark as
|
||||
@@ -153,7 +153,7 @@ const { data: todos, pending } = await useLazyAsyncData('todos', () => $fetch<{
|
||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple>
|
||||
<UButton
|
||||
icon="i-heroicons-view-columns"
|
||||
variant="soft"
|
||||
color="gray"
|
||||
size="xs"
|
||||
>
|
||||
Columns
|
||||
@@ -162,8 +162,7 @@ const { data: todos, pending } = await useLazyAsyncData('todos', () => $fetch<{
|
||||
|
||||
<UButton
|
||||
icon="i-heroicons-funnel"
|
||||
variant="soft"
|
||||
color="red"
|
||||
color="gray"
|
||||
size="xs"
|
||||
:disabled="search === '' && selectedStatus.length === 0"
|
||||
@click="resetFilters"
|
||||
@@ -181,6 +180,8 @@ const { data: todos, pending } = await useLazyAsyncData('todos', () => $fetch<{
|
||||
:loading="pending"
|
||||
sort-asc-icon="i-heroicons-arrow-up"
|
||||
sort-desc-icon="i-heroicons-arrow-down"
|
||||
class="w-full"
|
||||
:ui="{ td: { base: 'max-w-[0] truncate' } }"
|
||||
@select="select"
|
||||
>
|
||||
<template #completed-data="{ row }">
|
||||
@@ -212,7 +213,7 @@ const { data: todos, pending } = await useLazyAsyncData('todos', () => $fetch<{
|
||||
|
||||
<!-- Number of rows & Pagination -->
|
||||
<template #footer>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex flex-wrap justify-between items-center">
|
||||
<div>
|
||||
<span class="text-sm leading-5">
|
||||
Showing
|
||||
|
||||
40
docs/components/content/examples/TableExampleStyle.vue
Normal file
40
docs/components/content/examples/TableExampleStyle.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: '#'
|
||||
}, {
|
||||
key: 'quantity',
|
||||
label: 'Quantity',
|
||||
class: 'italic'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}]
|
||||
|
||||
const items = [{
|
||||
id: 1,
|
||||
name: 'Apple',
|
||||
quantity: { value: 100, class: 'bg-green-500/50 dark:bg-green-400/50' }
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Orange',
|
||||
quantity: { value: 0 },
|
||||
class: 'bg-red-500/50 dark:bg-red-400/50 animate-pulse'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Banana',
|
||||
quantity: { value: 30, class: 'bg-green-500/50 dark:bg-green-400/50' }
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Mango',
|
||||
quantity: { value: 5, class: 'bg-green-500/50 dark:bg-green-400/50' }
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="items" :columns="columns">
|
||||
<template #quantity-data="{ row }">
|
||||
{{ row.quantity.value }}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
@@ -24,9 +24,9 @@ function onSubmitPassword () {
|
||||
<template #account="{ item }">
|
||||
<UCard @submit.prevent="onSubmitAccount">
|
||||
<template #header>
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
<p class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Make changes to your account here. Click save when you're done.
|
||||
</p>
|
||||
|
||||
@@ -22,9 +22,9 @@ function onSubmit (form) {
|
||||
<template #item="{ item }">
|
||||
<UCard @submit.prevent="() => onSubmit(item.key === 'account' ? accountForm : passwordForm)">
|
||||
<template #header>
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
<p class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
const links = [{
|
||||
avatar: {
|
||||
src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/benjamincanac',
|
||||
srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/benjamincanac 2x'
|
||||
srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/benjamincanac 2x',
|
||||
alt: ''
|
||||
},
|
||||
label: 'benjamincanac',
|
||||
to: 'https://github.com/benjamincanac',
|
||||
@@ -10,7 +11,8 @@ const links = [{
|
||||
}, {
|
||||
avatar: {
|
||||
src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/Atinux',
|
||||
srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/Atinux 2x'
|
||||
srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/Atinux 2x',
|
||||
alt: ''
|
||||
},
|
||||
label: 'Atinux',
|
||||
to: 'https://github.com/Atinux',
|
||||
@@ -18,14 +20,13 @@ const links = [{
|
||||
}, {
|
||||
avatar: {
|
||||
src: 'https://ipx.nuxt.com/s_16x16/gh_avatar/smarroufin',
|
||||
srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/smarroufin 2x'
|
||||
srcset: 'https://ipx.nuxt.com/s_32x32/gh_avatar/smarroufin 2x',
|
||||
alt: ''
|
||||
},
|
||||
label: 'smarroufin',
|
||||
to: 'https://github.com/smarroufin',
|
||||
target: '_blank'
|
||||
}]
|
||||
|
||||
const { ui } = useAppConfig()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -33,8 +34,9 @@ const { ui } = useAppConfig()
|
||||
<template #avatar="{ link }">
|
||||
<UAvatar
|
||||
v-if="link.avatar"
|
||||
v-bind="{ size: ui.verticalNavigation.avatar.size, ...link.avatar }"
|
||||
:class="[ui.verticalNavigation.avatar.base]"
|
||||
v-bind="link.avatar"
|
||||
size="3xs"
|
||||
loading="lazy"
|
||||
/>
|
||||
<UIcon v-else name="i-heroicons-user-circle-20-solid" class="text-lg" />
|
||||
</template>
|
||||
|
||||
@@ -1,99 +1,206 @@
|
||||
<script setup lang="ts">
|
||||
const refs = ref([])
|
||||
const section = ref()
|
||||
|
||||
const { stop } = useIntersectionObserver(
|
||||
section,
|
||||
([{ isIntersecting }]) => {
|
||||
if (!isIntersecting) {
|
||||
return
|
||||
}
|
||||
|
||||
refs.value.forEach(element => element.style.animationPlayState = 'running')
|
||||
|
||||
stop()
|
||||
},
|
||||
{ threshold: 0.3 }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
refs.value.forEach((element) => {
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
element.style.animationFillMode = 'forwards'
|
||||
element.style.transformOrigin = 'center'
|
||||
element.style.animationPlayState = 'paused'
|
||||
element.style.animationDuration = '1s'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition appear name="fade">
|
||||
<ULandingGrid class="lg:grid-cols-10 lg:gap-8">
|
||||
<div class="col-span-8 flex items-center">
|
||||
<RangeExample />
|
||||
</div>
|
||||
<ULandingGrid ref="section" class="lg:grid-cols-10 lg:gap-8">
|
||||
<div :ref="(el) => (refs[1] = el)" class="col-span-8 flex items-center animate-top">
|
||||
<RangeExample />
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 row-span-2 flex items-center">
|
||||
<RadioExample />
|
||||
</div>
|
||||
<div :ref="(el) => (refs[2] = el)" class="col-span-2 row-span-2 flex items-center animate-right">
|
||||
<RadioExample />
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<DropdownExampleBasic :popper="{ placement: 'bottom-start', strategy: 'absolute' }" />
|
||||
</div>
|
||||
<div :ref="(el) => (refs[4] = el)" class="col-span-2 animate-left z-10">
|
||||
<DropdownExampleBasic :popper="{ placement: 'bottom-start', strategy: 'absolute' }" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 flex flex-wrap items-center justify-between gap-1">
|
||||
<UAvatarGroup :max="2">
|
||||
<UAvatar
|
||||
src="https://ipx.nuxt.com/s_32x32/gh_avatar/benjamincanac"
|
||||
srcset="https://ipx.nuxt.com/s_64x64/gh_avatar/benjamincanac 2x"
|
||||
alt="benjamincanac"
|
||||
/>
|
||||
<UAvatar
|
||||
src="https://ipx.nuxt.com/s_32x32/gh_avatar/Atinux"
|
||||
srcset="https://ipx.nuxt.com/s_64x64/gh_avatar/Atinux 2x"
|
||||
alt="Atinux"
|
||||
/>
|
||||
<UAvatar
|
||||
src="https://ipx.nuxt.com/s_32x32/gh_avatar/smarroufin"
|
||||
srcset="https://ipx.nuxt.com/s_64x64/gh_avatar/smarroufin 2x"
|
||||
alt="smarroufin"
|
||||
/>
|
||||
</UAvatarGroup>
|
||||
<div
|
||||
:ref="(el) => (refs[3] = el)"
|
||||
class="col-span-6 flex flex-wrap items-center justify-between gap-1 animate-bottom"
|
||||
>
|
||||
<UAvatarGroup :max="2">
|
||||
<UAvatar
|
||||
src="https://ipx.nuxt.com/s_32x32/gh_avatar/benjamincanac"
|
||||
srcset="https://ipx.nuxt.com/s_64x64/gh_avatar/benjamincanac 2x"
|
||||
alt="benjamincanac"
|
||||
width="40"
|
||||
height="40"
|
||||
loading="lazy"
|
||||
/>
|
||||
<UAvatar
|
||||
src="https://ipx.nuxt.com/s_32x32/gh_avatar/Atinux"
|
||||
srcset="https://ipx.nuxt.com/s_64x64/gh_avatar/Atinux 2x"
|
||||
alt="Atinux"
|
||||
width="40"
|
||||
height="40"
|
||||
loading="lazy"
|
||||
/>
|
||||
<UAvatar
|
||||
src="https://ipx.nuxt.com/s_32x32/gh_avatar/smarroufin"
|
||||
srcset="https://ipx.nuxt.com/s_64x64/gh_avatar/smarroufin 2x"
|
||||
alt="smarroufin"
|
||||
width="40"
|
||||
height="40"
|
||||
loading="lazy"
|
||||
/>
|
||||
</UAvatarGroup>
|
||||
|
||||
<UButton label="Button" icon="i-heroicons-pencil-square" />
|
||||
<UButton label="Button" icon="i-heroicons-pencil-square" />
|
||||
|
||||
<UBadge label="Badge" />
|
||||
<UBadge label="Badge" />
|
||||
|
||||
<UColorModeToggle />
|
||||
<UColorModeToggle />
|
||||
|
||||
<PaginationExampleBasic />
|
||||
</div>
|
||||
<PaginationExampleBasic />
|
||||
</div>
|
||||
|
||||
<div class="col-span-3 row-span-8 gap-6 flex flex-col justify-between">
|
||||
<UNotification :id="1" title="Notification" description="This is a notification!" icon="i-heroicons-command-line" />
|
||||
<div :ref="(el) => (refs[5] = el)" class="col-span-3 row-span-8 gap-6 flex flex-col justify-between animate-left">
|
||||
<UNotification
|
||||
:id="1"
|
||||
title="Notification"
|
||||
description="This is a notification!"
|
||||
icon="i-heroicons-command-line"
|
||||
:ui="{ shadow: 'shadow' }"
|
||||
:close-button="null"
|
||||
:timeout="30000"
|
||||
/>
|
||||
|
||||
<TabsExampleItemCustomSlot />
|
||||
<TabsExampleItemCustomSlot />
|
||||
|
||||
<UCard class="flex-shrink-0">
|
||||
<div class="flex items-center gap-4 justify-center">
|
||||
<USkeleton class="h-14 w-14 flex-shrink-0" :ui="{ rounded: 'rounded-full' }" />
|
||||
<div class="space-y-3 flex-1">
|
||||
<USkeleton class="h-4 w-full" />
|
||||
<USkeleton class="h-4 w-2/3" />
|
||||
</div>
|
||||
<UCard class="flex-shrink-0">
|
||||
<div class="flex items-center gap-4 justify-center">
|
||||
<USkeleton class="h-14 w-14 flex-shrink-0" :ui="{ rounded: 'rounded-full' }" />
|
||||
<div class="space-y-3 flex-1">
|
||||
<USkeleton class="h-4 w-full" />
|
||||
<USkeleton class="h-4 w-2/3" />
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div class="col-span-5 row-span-2 flex flex-col">
|
||||
<UCard :ui="{ body: { base: 'flex-1 flex flex-col overflow-y-auto', padding: '' } }" class="col-span-4 row-span-6 flex-1 flex flex-col">
|
||||
<CommandPaletteExampleGroups />
|
||||
</UCard>
|
||||
</div>
|
||||
<div :ref="(el) => (refs[6] = el)" class="col-span-5 row-span-2 flex flex-col animate-bottom">
|
||||
<UCard
|
||||
:ui="{ body: { base: 'flex-1 flex flex-col overflow-y-auto', padding: '' } }"
|
||||
class="col-span-4 row-span-6 flex-1 flex flex-col"
|
||||
>
|
||||
<CommandPaletteExampleGroups />
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 row-span-2 gap-6 flex flex-col">
|
||||
<CheckboxExample />
|
||||
<div :ref="(el) => (refs[7] = el)" class="col-span-2 row-span-2 gap-6 flex flex-col animate-right z-10">
|
||||
<CheckboxExample />
|
||||
|
||||
<InputExampleClearable />
|
||||
<InputExampleClearable />
|
||||
|
||||
<UFormGroup label="Labels">
|
||||
<SelectMenuExampleCreatable />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Labels">
|
||||
<SelectMenuExampleCreatable />
|
||||
</UFormGroup>
|
||||
|
||||
<UCard :ui="{ body: { padding: '!p-1' } }">
|
||||
<VerticalNavigationExampleAvatarSlot />
|
||||
</UCard>
|
||||
</div>
|
||||
<UCard :ui="{ body: { padding: '!p-1' } }">
|
||||
<VerticalNavigationExampleAvatarSlot />
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div class="col-span-7 row-span-6">
|
||||
<UCard :ui="{ body: { padding: '' } }">
|
||||
<TableExampleClickable :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" />
|
||||
</UCard>
|
||||
</div>
|
||||
</ULandingGrid>
|
||||
</Transition>
|
||||
<div :ref="(el) => (refs[8] = el)" class="col-span-7 row-span-6 animate-bottom">
|
||||
<UCard :ui="{ body: { padding: '' } }">
|
||||
<TableExampleClickable :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" />
|
||||
</UCard>
|
||||
</div>
|
||||
</ULandingGrid>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
<style scoped lang="postcss">
|
||||
.animate-top {
|
||||
animation: translateDown;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
.animate-bottom {
|
||||
animation: translateUp;
|
||||
}
|
||||
|
||||
.animate-left {
|
||||
animation: translateLeft;
|
||||
}
|
||||
|
||||
.animate-right {
|
||||
animation-name: translateRight;
|
||||
}
|
||||
|
||||
@keyframes translateDown {
|
||||
0% {
|
||||
transform: translate3D(0, -100px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes translateUp {
|
||||
0% {
|
||||
transform: translate3D(0, 100px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes translateLeft {
|
||||
0% {
|
||||
transform: translate3D(-100px, 0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3D(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes translateRight {
|
||||
0% {
|
||||
transform: translate3D(100px, 0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3D(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,13 +8,19 @@
|
||||
>
|
||||
<div
|
||||
ref="el"
|
||||
class="absolute inset-0 grid justify-center auto-rows-[var(--cell)] -space-y-px"
|
||||
class="absolute inset-0 grid justify-center auto-rows-[--cell] -space-y-px"
|
||||
>
|
||||
<div v-for="(row, rowIndex) in grid" :key="rowIndex" class="grid grid-flow-col auto-cols-[--cell] flex-1 -space-x-px">
|
||||
<div v-for="(cell, cellIndex) in row" :key="cellIndex" class="transition-[background] duration-1000 border border-primary-200/50 dark:border-primary-900/25 bg-opacity-10 hover:bg-opacity-20 dark:bg-opacity-5 dark:hover:bg-opacity-10" :class="[cell && `bg-primary-500 dark:bg-primary-400 cursor-pointer`]" @click="removeCell(rowIndex, cellIndex)" />
|
||||
<div
|
||||
v-for="(cell, cellIndex) in row"
|
||||
:key="cellIndex"
|
||||
class="relative border border-primary-200/50 dark:border-primary-900/25"
|
||||
>
|
||||
<div class="absolute inset-0 bg-primary-500/10 hover:bg-primary-500/20 dark:bg-primary-400/5 dark:hover:bg-primary-400/10 opacity-0 transition-opacity will-change-[opacity] duration-1000" :class="[cell && 'opacity-100 cursor-pointer']" @click="cell && removeCell(rowIndex, cellIndex)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-[calc((var(--cell)*var(--rows))+1px)] inset-x-0 h-[calc(var(--cell)*2)] bg-gradient-to-t from-white dark:from-gray-900" />
|
||||
<div class="absolute top-[calc((var(--cell)*var(--rows))+1px)] inset-x-0 h-[calc(var(--cell)*2)] bg-gradient-to-t from-white dark:from-gray-900 pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -28,13 +34,8 @@ const grid = ref([])
|
||||
const rows = ref(0)
|
||||
const cols = ref(0)
|
||||
|
||||
const colors = useAppConfig()?.ui.colors
|
||||
const { width, height } = useElementSize(el)
|
||||
|
||||
function getRandomColor () {
|
||||
return colors[Math.floor(Math.random() * (colors.length - 1))]
|
||||
}
|
||||
|
||||
function createGrid () {
|
||||
grid.value = []
|
||||
|
||||
@@ -44,10 +45,9 @@ function createGrid () {
|
||||
}
|
||||
|
||||
function createNewCell () {
|
||||
const color = getRandomColor()
|
||||
const x = Math.floor(Math.random() * cols.value)
|
||||
|
||||
grid.value[0][x] = color
|
||||
grid.value[0][x] = true
|
||||
}
|
||||
|
||||
function moveCellsDown () {
|
||||
|
||||
@@ -1,57 +1,43 @@
|
||||
import type { NavItem, ParsedContent } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
export const useContentSource = () => {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const config = useRuntimeConfig().public
|
||||
|
||||
const branches = [{
|
||||
const branches = computed(() => [{
|
||||
id: 'dev',
|
||||
name: 'dev',
|
||||
icon: 'i-heroicons-cube',
|
||||
suffix: 'dev',
|
||||
label: 'Edge'
|
||||
label: 'Edge',
|
||||
disabled: route.path.startsWith('/dev'),
|
||||
click: () => select({ name: 'dev' })
|
||||
}, {
|
||||
id: 'main',
|
||||
name: 'main',
|
||||
icon: 'i-heroicons-cube',
|
||||
suffix: 'latest',
|
||||
label: `v${config.version}`
|
||||
}]
|
||||
label: `v${config.version}`,
|
||||
disabled: !route.path.startsWith('/dev'),
|
||||
click: () => select({ name: 'main' })
|
||||
}])
|
||||
|
||||
const branch = computed(() => branches.find(b => b.name === (route.path.startsWith('/dev') ? 'dev' : 'main')))
|
||||
const branch = computed(() => branches.value.find(b => b.name === (route.path.startsWith('/dev') ? 'dev' : 'main')))
|
||||
|
||||
const prefix = computed(() => `/${branch.value.name}`)
|
||||
|
||||
function removePrefixFromNavigation (navigation: NavItem[]): NavItem[] {
|
||||
return navigation.map((link) => {
|
||||
const { _path, children, ...rest } = link
|
||||
|
||||
return {
|
||||
...rest,
|
||||
_path: route.path.startsWith(prefix.value) ? _path : _path.replace(new RegExp(`^${prefix.value}`, 'g'), ''),
|
||||
children: children?.length ? removePrefixFromNavigation(children) : undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function removePrefixFromFiles (files: ParsedContent[]) {
|
||||
return files.map((file) => {
|
||||
if (!file) {
|
||||
function select (branch) {
|
||||
if (branch.name === 'dev') {
|
||||
if (route.path.startsWith('/dev')) {
|
||||
return
|
||||
}
|
||||
|
||||
const { _path, ...rest } = file
|
||||
|
||||
return {
|
||||
...rest,
|
||||
_path: route.path.startsWith(prefix.value) ? _path : _path.replace(new RegExp(`^${prefix.value}`, 'g'), '')
|
||||
}
|
||||
})
|
||||
router.push(`/dev${route.path}`)
|
||||
} else {
|
||||
router.push(route.path.replace('/dev', ''))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
branches,
|
||||
branch,
|
||||
prefix,
|
||||
removePrefixFromNavigation,
|
||||
removePrefixFromFiles
|
||||
select
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ This module has been developed by the [NuxtLabs](https://nuxtlabs.com/) team for
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ description: 'Learn how to install and configure the module in your Nuxt app.'
|
||||
::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm i -D @nuxt/ui
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add -D @nuxt/ui
|
||||
yarn add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install -D @nuxt/ui
|
||||
npm install @nuxt/ui
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: 'Learn how to customize the look and feel of the components.'
|
||||
navigation:
|
||||
badge: 'New'
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Overview
|
||||
@@ -39,7 +39,7 @@ Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it
|
||||
We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
|
||||
::
|
||||
|
||||
The `primary` color also has a `DEFAULT` shade that changes based on the theme. It is `500` in light mode and `400` in dark mode. You can use as a shortcut in your components and pages, e.g. `text-primary`, `bg-primary`, `focus-visible:ring-primary`, etc. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
The `primary` color also has a `DEFAULT` shade that changes based on the theme. It is `500` in light mode and `400` in dark mode. You can use as a shortcut in your components and pages, e.g. `text-primary`, `bg-primary`, `focus-visible:ring-primary`, etc.
|
||||
|
||||
### Smart Safelisting
|
||||
|
||||
@@ -81,7 +81,7 @@ This can also happen when you bind a dynamic color to a component: `<UBadge :col
|
||||
|
||||
### `app.config.ts`
|
||||
|
||||
Components are styled with Tailwind CSS but classes are all defined in the default [app.config.ts](https://github.com/nuxt/ui/blob/dev/src/runtime/app.config.ts) file. You can override those in your own `app.config.ts`.
|
||||
Components are styled with Tailwind CSS but classes are all defined in the default [ui.config.ts](https://github.com/nuxt/ui/blob/dev/src/runtime/ui.config.ts) file. You can override those in your own `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
@@ -93,6 +93,25 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `app.config.ts` is smartly merged with the default config. This means you don't have to rewrite everything.
|
||||
|
||||
You can change this behaviour by setting `strategy` to `override` in your `app.config.ts`: :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
strategy: 'override',
|
||||
button: {
|
||||
color: {
|
||||
white: {
|
||||
solid: 'bg-white dark:bg-gray-900'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `ui` prop
|
||||
|
||||
Each component has a `ui` prop that allows you to customize everything specifically.
|
||||
@@ -109,31 +128,42 @@ Each component has a `ui` prop that allows you to customize everything specifica
|
||||
You can find the default classes for each component under the `Preset` section.
|
||||
::
|
||||
|
||||
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `ui` prop is smartly merged with the config. This means you don't have to rewrite everything. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `ui` prop is smartly merged with the config. This means you don't have to rewrite everything.
|
||||
|
||||
For example, the default preset of the `FormGroup` component looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"label": {
|
||||
"base": "block font-medium text-gray-700 dark:text-gray-200",
|
||||
...
|
||||
"base": "block font-medium text-gray-700 dark:text-gray-200"
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
To change the font of the `label`, you only need to write:
|
||||
|
||||
```vue
|
||||
<UFormGroup name="email" label="Email" :ui="{ label: { base: 'font-semibold' } }">
|
||||
...
|
||||
</UFormGroup>
|
||||
<UFormGroup name="email" label="Email" :ui="{ label: { base: 'font-semibold' } }" />
|
||||
```
|
||||
|
||||
This will smartly replace the `font-medium` by `font-semibold` and prevent any class duplication and any class priority issue.
|
||||
|
||||
You can change this behaviour by setting `strategy` to `override` inside the `ui` prop: :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
|
||||
```vue
|
||||
<UButton
|
||||
to="https://github.com/nuxt/ui"
|
||||
:ui="{
|
||||
strategy: 'override',
|
||||
color: {
|
||||
white: {
|
||||
solid: 'bg-white dark:bg-gray-900'
|
||||
}
|
||||
}
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
### `class` attribute
|
||||
|
||||
You can also use the `class` attribute to add classes to the component.
|
||||
@@ -144,7 +174,7 @@ You can also use the `class` attribute to add classes to the component.
|
||||
</template>
|
||||
```
|
||||
|
||||
Again, with [tailwind-merge](https://github.com/dcastil/tailwind-merge), this will smartly merge the classes with the `ui` prop and the config. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
Again, with [tailwind-merge](https://github.com/dcastil/tailwind-merge), this will smartly merge the classes with the `ui` prop and the config.
|
||||
|
||||
### Default values
|
||||
|
||||
@@ -229,15 +259,15 @@ However, you will need to install either `@iconify/json` (full icon collections,
|
||||
::code-group
|
||||
|
||||
```bash [yarn]
|
||||
yarn add -D @iconify-json/{collection_name}
|
||||
yarn add @iconify-json/{collection_name}
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install -D @iconify-json/{collection_name}
|
||||
npm install @iconify-json/{collection_name}
|
||||
```
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm i -D @iconify-json/{collection_name}
|
||||
pnpm i @iconify-json/{collection_name}
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
188
docs/content/1.getting-started/6.contributing.md
Normal file
188
docs/content/1.getting-started/6.contributing.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
title: Contributing
|
||||
description: Learn how to contribute to Nuxt UI.
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Nuxt UI thrives thanks to its fantastic community ❤️, which contributes by submitting issues, creating pull requests, and offering valuable feedback.
|
||||
|
||||
Before reporting a bug or reporting a feature, please make sure that you have read through our documentation and existing [issues](https://github.com/nuxt/ui/issues).
|
||||
|
||||
## Submitting a Pull Request
|
||||
|
||||
### 1. Before You Start
|
||||
|
||||
Check if there's an existing issue describing the problem or feature request you're working on. If there is, please leave a comment on the issue to let us know you're working on it.
|
||||
|
||||
If there isn't, open a new issue to discuss the problem or feature.
|
||||
|
||||
### 2. Local Development Setup
|
||||
|
||||
To begin local development, follow these steps:
|
||||
|
||||
1. Clone the `nuxt/ui` repository to your local machine:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nuxt/ui.git
|
||||
```
|
||||
|
||||
2. Install dependencies and prepare the project:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm run dev:prepare
|
||||
```
|
||||
|
||||
3. To configure your local development environment, use the following commands:
|
||||
|
||||
- To work on the **documentation** in the `docs` folder, run:
|
||||
|
||||
```sh
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
- To test the components located in the `playground` folder within `app.vue`, run:
|
||||
|
||||
```sh
|
||||
pnpm run play
|
||||
```
|
||||
|
||||
#### IDE Setup
|
||||
|
||||
We recommend using VS Code along with the ESLint extension. You can enable auto-fix and formatting when saving your code. Here's how:
|
||||
|
||||
```json
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": false,
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also use the `lint` command:
|
||||
|
||||
```sh
|
||||
pnpm run lint # check for linting errors
|
||||
pnpm run lint:fix # fix linting errors
|
||||
```
|
||||
|
||||
#### No Prettier
|
||||
|
||||
Since ESLint is already configured to format the code, there's no need for duplicating functionality with Prettier.
|
||||
|
||||
If you have Prettier installed in your editor, we recommend disabling it to avoid conflicts.
|
||||
|
||||
#### Type Checking
|
||||
|
||||
We use TypeScript for type checking. You can use the `typecheck` command to check for type errors:
|
||||
|
||||
```sh
|
||||
pnpm run typecheck
|
||||
```
|
||||
|
||||
### 3. Commit Conventions
|
||||
|
||||
Use Conventional Commits for commit messages with the following format:
|
||||
|
||||
```
|
||||
<type>(optional scope): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
#### Types
|
||||
|
||||
- `feat` : for new features.
|
||||
- `fix` : for bug fixes.
|
||||
- `refactor` : for code changes that are neither bug fixes nor new features.
|
||||
- `perf` : for code refactoring that improves performance.
|
||||
- `test` : for code related to automatic testing.
|
||||
- `style` : for refactoring related to code style (not for CSS).
|
||||
- `docs` : for changes related to documentation.
|
||||
- `chore` : for anything else.
|
||||
|
||||
#### Scope
|
||||
|
||||
Where the change occurred (e.g., `Table`, `Alert`, `Accordion`, etc.).
|
||||
|
||||
#### Description
|
||||
|
||||
A summary of the changes made.
|
||||
|
||||
#### Examples
|
||||
|
||||
```
|
||||
feat(Alert): new component
|
||||
chore(Table): improve accessibility
|
||||
docs: migrate to @nuxt/ui-pro
|
||||
```
|
||||
|
||||
### 4. Making the Pull Request
|
||||
|
||||
- Follow the guide for creating a pull request and ensure your PR's title adheres to the Commit Convention. Mention any related issues in the PR description.
|
||||
|
||||
- Multiple commits are fine; no need to rebase or force push. We'll use `Squash and Merge` when merging.
|
||||
|
||||
- Ensure linting and make tests manually before submitting the PR. Avoid making unrelated changes.
|
||||
|
||||
### 5. After You've Made a Pull Request
|
||||
|
||||
We'll review it promptly. If assigned to a maintainer, they'll review it carefully. Ignore the red text; it's for tracking purposes.
|
||||
|
||||
## Project Structure
|
||||
|
||||
In this project, you'll find a variety of folders and files that serve different purposes. Here's an overview of the main ones:
|
||||
|
||||
- **Documentation - `docs`** :
|
||||
|
||||
The documentation is located in the `docs` folder. It's a Nuxt app that uses the `@nuxt/content` module to generate the documentation pages from Markdown files. Here's a breakdown of its structure:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── components/
|
||||
│ ├── examples/ # Components used in documentation as examples
|
||||
│ └── themes/ # Components used in the examples page in the theming section
|
||||
├── content/ # Documentation, separated into categories according to component types
|
||||
│ ├── 1.getting-started/
|
||||
│ │ ├── 1.index.md
|
||||
│ │ ├── 2.installation.md
|
||||
│ │ ├── ... etc
|
||||
│ ├── 2.elements/ # The category of components, which are elements
|
||||
│ │ ├── 1.accordion.md # Docs for a single component (i.e., accordion)
|
||||
│ │ ├── 2.alert.md
|
||||
│ │ ├── ... etc
|
||||
└── ... etc
|
||||
```
|
||||
|
||||
- **Components - `src`** :
|
||||
|
||||
The components are located in the `src` folder. It's separated into categories according to component types. Here's a breakdown of its structure:
|
||||
|
||||
```
|
||||
src/
|
||||
├── runtime/
|
||||
│ ├── composables/ # Composable functions used in components
|
||||
│ ├── components/ # Components folder, separated into categories according to component types
|
||||
│ │ ├── data/ # The category of components, which are data related
|
||||
│ │ │ ├── table.vue/ # Table component
|
||||
│ │ │ ├── elements/ # Elements category
|
||||
│ │ │ │ ├── ...etc/
|
||||
│ │ │ └── ... etc/
|
||||
│ │ ├── plugins/ # Plugins used in components
|
||||
│ │ ├── utils/ # Utility functions used on the components page (e.g., lodash)
|
||||
│ │ ├── types/ # Types used in components
|
||||
│ │ │ ├── accordion.d.ts/ # [componentName].d.ts type used for single component
|
||||
│ │ │ ├── avatar.d.ts/
|
||||
│ │ │ └── ... etc/
|
||||
│ │ ├── ui.config.ts/ # Configuration file used to apply styles to every component
|
||||
├── colors.ts/ # Everything related to color functions (e.g., safelistByComponent, generateSafelist)
|
||||
└── ... etc/ # Other files and folders
|
||||
```
|
||||
|
||||
## Thanks
|
||||
|
||||
Thank you again for being interested in this project! You are awesome! ❤️
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Roadmap
|
||||
description: Discover our Volta board for @nuxt/ui development status.
|
||||
toc: false
|
||||
---
|
||||
|
||||
:volta-embed{token="eyJ2aWV3IjoiYm9hcmQiLCJib2FyZFN0YXR1c2VzIjpbInRyaWFnZSIsImJhY2tsb2ciLCJ0b2RvIiwiaW5fcHJvZ3Jlc3MiLCJpbl9yZXZpZXciLCJkb25lIiwicmVsZWFzZWQiXSwiYm9hcmRMaW5rZWRQcnMiOnRydWUsImxpc3RHcm91cCI6InN0YXRlIiwibGlzdE9yZGVyIjoiY3JlYXRlZF9hdCIsInRpbWVsaW5lWm9vbSI6Im1vbnRoIiwidGltZWxpbmVPcmRlciI6InN0YXRlIiwidGltZWxpbmVEaXNwbGF5IjoiYWxsX21pbGVzdG9uZXMiLCJmaWx0ZXJzIjp7fSwib3duZXIiOiJudXh0bGFicyIsIm5hbWUiOiJ1aSJ9"}
|
||||
7
docs/content/1.getting-started/7.roadmap.md
Normal file
7
docs/content/1.getting-started/7.roadmap.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Roadmap
|
||||
description: Discover our Volta board for @nuxt/ui development status.
|
||||
toc: false
|
||||
---
|
||||
|
||||
:volta-embed{token="eyJ2aWV3IjoiYm9hcmQiLCJib2FyZFN0YXR1c2VzIjpbInRyaWFnZSIsImJhY2tsb2ciLCJ0b2RvIiwiaW5fcHJvZ3Jlc3MiLCJpbl9yZXZpZXciLCJkb25lIiwicmVsZWFzZWQiLCJjYW5jZWxsZWQiXSwiYm9hcmRMaW5rZWRQcnMiOnRydWUsImxpc3RHcm91cCI6InN0YXR1cyIsImxpc3RPcmRlciI6ImNyZWF0ZWRfYXQiLCJ0aW1lbGluZVpvb20iOiJtb250aCIsInRpbWVsaW5lT3JkZXIiOiJzdGF0ZSIsInRpbWVsaW5lRGlzcGxheSI6ImFsbF9taWxlc3RvbmVzIiwiZmlsdGVycyI6e30sIm93bmVyIjoibnV4dCIsIm5hbWUiOiJ1aSJ9"}
|
||||
@@ -263,8 +263,8 @@ const items = [{
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<code>$ npm install @nuxt/ui</code>
|
||||
<code>$ nnpm install -D @nuxt/ui</code>
|
||||
<code>$ pnpm i -D @nuxt/ui</code>
|
||||
<code>$ yarn add @nuxt/ui</code>
|
||||
<code>$ pnpm add @nuxt/ui</code>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
|
||||
@@ -4,8 +4,6 @@ links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Avatar.vue
|
||||
navigation:
|
||||
badge: 'New'
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -55,7 +53,7 @@ baseProps:
|
||||
|
||||
If there is an error loading the `src` of the avatar or `src` is null / false a background placeholder will be displayed, customizable in `ui.avatar.background`.
|
||||
|
||||
#### Icon :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
#### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.avatar.default.icon` to display an icon on top of the background.
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Button.vue
|
||||
navigation:
|
||||
badge: 'New'
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -278,7 +276,7 @@ excludedProps:
|
||||
To stack buttons as a group, use the `ButtonGroup` component.
|
||||
|
||||
- To size all the buttons equally, pass the `size` prop
|
||||
- To change the orientation of the buttons, set the `orientation` prop to `vertical` :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
- To change the orientation of the buttons, set the `orientation` prop to `vertical`
|
||||
- To adjust the rounded or the shadow around buttons, customize with `ui.buttonGroup.rounded` or `ui.buttonGroup.shadow`
|
||||
|
||||
::component-card{slug="ButtonGroup"}
|
||||
|
||||
@@ -17,6 +17,6 @@ The Link component is a wrapper around [`<NuxtLink>`](https://nuxt.com/docs/api/
|
||||
|
||||
The incentive behind this is to provide the same API as NuxtLink back in Nuxt 2 / Vue 2. You can read more about it in the Vue Router [migration from Vue 2](https://router.vuejs.org/guide/migration/#removal-of-the-exact-prop-in-router-link) guide.
|
||||
|
||||
It also renders an `<a>` tag when a `to` prop is provided, otherwise it renders a `<button>` tag.
|
||||
It also renders an `<a>` tag when a `to` prop is provided, otherwise it defaults to rendering a `<button>` tag. The default behavior can be customized using the `as` prop.
|
||||
|
||||
It is used underneath by the [Button](/elements/button), [Dropdown](/elements/dropdown) and [VerticalNavigation](/navigation/vertical-navigation) components.
|
||||
|
||||
@@ -4,8 +4,6 @@ links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Form.vue
|
||||
navigation:
|
||||
badge: 'New'
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -234,7 +232,7 @@ async function submit (event: FormSubmitEvent<any>) {
|
||||
```
|
||||
::
|
||||
|
||||
### Valibot :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
### Valibot
|
||||
|
||||
::component-example
|
||||
#default
|
||||
|
||||
@@ -375,7 +375,7 @@ const selected = ref(people[3])
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton color="gray">
|
||||
<UButton color="gray" class="flex-1 justify-between">
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
|
||||
|
||||
@@ -190,6 +190,115 @@ code: >-
|
||||
This will only work with form elements that support the `size` prop.
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `label`
|
||||
|
||||
Use the `#label` slot to set the custom content for label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
label: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
---
|
||||
|
||||
#label
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mr-2 inline-flex"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `description`
|
||||
|
||||
Use the `#description` slot to set the custom content for description.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
description: Write only valid email address <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
props:
|
||||
label: 'Email'
|
||||
---
|
||||
|
||||
#description
|
||||
Write only valid email address :u-icon{name="i-heroicons-information-circle" class="align-middle"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `hint`
|
||||
|
||||
Use the `#hint` slot to set the custom content for hint.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
hint: <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
props:
|
||||
label: 'Step 1'
|
||||
---
|
||||
|
||||
#hint
|
||||
:u-icon{name="i-heroicons-arrow-right-20-solid"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `help`
|
||||
|
||||
Use the `#help` slot to set the custom content for help.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
help: Here are some examples <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
props:
|
||||
label: 'Email'
|
||||
---
|
||||
|
||||
#help
|
||||
Here are some examples :u-icon{name="i-heroicons-information-circle" class="align-middle"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `error`
|
||||
|
||||
Use the `#error` slot to set the custom content for error.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-group-error-slot-example{class="w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UFormGroup label="Email" :error="!email && 'You must enter an email'" help="This is a nice email!">
|
||||
<template #default="{ error }">
|
||||
<UInput v-model="email" type="email" placeholder="Enter email" :trailing-icon="error ? 'i-heroicons-exclamation-triangle-20-solid' : undefined" />
|
||||
</template>
|
||||
|
||||
<template #error="{ error }">
|
||||
<UAlert v-if="error" icon="i-heroicons-exclamation-triangle-20-solid" :title="error" color="red" />
|
||||
<UAlert v-else icon="i-heroicons-check-circle-20-solid" title="Your email is valid" color="green" />
|
||||
</template>
|
||||
</UFormGroup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const email = ref('')
|
||||
</script>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -524,6 +524,65 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
## Styling
|
||||
|
||||
You can apply styles to `tr` and `td` elements by passing a `class` to rows.
|
||||
|
||||
Also, you can apply styles to `th` elements by passing a `class` to columns.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-style{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: '#'
|
||||
}, {
|
||||
key: 'quantity',
|
||||
label: 'Quantity',
|
||||
class: 'italic' // Apply style to column header
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}]
|
||||
|
||||
const items = [{
|
||||
id: 1,
|
||||
name: 'Apple',
|
||||
quantity: { value: 100, class: 'bg-green-500/50 dark:bg-green-400/50' } // Apply style to td
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Orange',
|
||||
quantity: { value: 0 },
|
||||
class: 'bg-red-500/50 dark:bg-red-400/50 animate-pulse' // Apply style to tr
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Banana',
|
||||
quantity: { value: 30, class: 'bg-green-500/50 dark:bg-green-400/50' }
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Mango',
|
||||
quantity: { value: 5, class: 'bg-green-500/50 dark:bg-green-400/50' }
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="items" :columns="columns">
|
||||
<template #quantity-data="{ row }">
|
||||
{{ row.quantity.value }}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
You can use slots to customize the header and data cells of the table.
|
||||
|
||||
@@ -125,8 +125,6 @@ const links = [{
|
||||
to: 'https://github.com/benjamincanac',
|
||||
target: '_blank'
|
||||
}, ...]
|
||||
|
||||
const { ui } = useAppConfig()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -134,8 +132,8 @@ const { ui } = useAppConfig()
|
||||
<template #avatar="{ link }">
|
||||
<UAvatar
|
||||
v-if="link.avatar"
|
||||
v-bind="{ size: ui.verticalNavigation.avatar.size, ...link.avatar }"
|
||||
:class="[ui.verticalNavigation.avatar.base]"
|
||||
v-bind="link.avatar"
|
||||
size="3xs"
|
||||
/>
|
||||
<UIcon v-else name="i-heroicons-user-circle-20-solid" class="text-lg" />
|
||||
</template>
|
||||
|
||||
@@ -4,8 +4,6 @@ links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/Tabs.vue
|
||||
navigation:
|
||||
badge: 'New'
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -87,7 +85,7 @@ const items = [...]
|
||||
This will have no effect if you are using a `v-model` to control the selected index.
|
||||
::
|
||||
|
||||
### Listen to changes :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
### Listen to changes
|
||||
|
||||
You can listen to changes by using the `@change` event. The event will emit the index of the selected item.
|
||||
|
||||
@@ -113,7 +111,7 @@ function onChange (index) {
|
||||
```
|
||||
::
|
||||
|
||||
### Control the selected index :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
### Control the selected index
|
||||
|
||||
Use a `v-model` to control the selected index.
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ links:
|
||||
- label: 'Dialog'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/dialog'
|
||||
navigation:
|
||||
badge: 'New'
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -180,7 +178,7 @@ defineShortcuts({
|
||||
</script>
|
||||
```
|
||||
|
||||
### Fullscreen :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
### Fullscreen
|
||||
|
||||
Set the `fullscreen` prop to `true` to enable it.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ hero:
|
||||
description: 'Nuxt UI simplifies the creation of stunning and responsive web applications with its<br class="hidden lg:block"> comprehensive collection of fully styled and customizable UI components designed for Nuxt.'
|
||||
sections:
|
||||
- slot: demo
|
||||
class: 'hidden lg:block dark:bg-gradient-to-b from-gray-900 to-gray-950/50 !pt-0'
|
||||
class: 'hidden lg:block dark:bg-gradient-to-b from-gray-900 to-gray-950/50 !pt-12'
|
||||
- title: Everything you expect from a<br class="hidden lg:block"> <span class="text-primary">UI component library</span>
|
||||
slot: features
|
||||
class: 'dark:bg-gradient-to-b from-gray-900 to-gray-950/50 dark:lg:bg-none dark:lg:bg-gray-950/50'
|
||||
@@ -16,27 +16,39 @@ sections:
|
||||
icon: i-heroicons-swatch
|
||||
to: /getting-started/theming#colors
|
||||
class: 'col-span-7 row-span-3'
|
||||
image: /illustrations/color-palette
|
||||
image:
|
||||
path: /illustrations/color-palette
|
||||
width: 363
|
||||
height: 152
|
||||
orientation: 'horizontal'
|
||||
- title: Fully Customizable
|
||||
description: 'Change the style of any component in your App Config or customize them specifically through the ui prop.'
|
||||
icon: i-heroicons-wrench-screwdriver
|
||||
to: /getting-started/theming#components
|
||||
image: /illustrations/fully-customizable
|
||||
image:
|
||||
path: /illustrations/fully-customizable
|
||||
width: 444
|
||||
height: 160
|
||||
class: 'col-span-5 row-span-5 lg:mb-10'
|
||||
orientation: 'vertical'
|
||||
- title: Icons
|
||||
description: 'Choose any of the 100k+ icons from the most popular icon libraries with the Icon component or the icon prop.'
|
||||
icon: i-heroicons-face-smile
|
||||
to: /getting-started/theming#icons
|
||||
image: /illustrations/icon-library
|
||||
image:
|
||||
path: /illustrations/icon-library
|
||||
width: 362
|
||||
height: 184
|
||||
class: 'col-span-7 row-span-3'
|
||||
orientation: 'horizontal'
|
||||
- title: Light & Dark
|
||||
description: 'Every component is designed with dark mode in mind. Works out of the box with @nuxtjs/color-mode.'
|
||||
to: /getting-started/theming#dark-mode
|
||||
icon: i-heroicons-moon
|
||||
image: /illustrations/dark-mode
|
||||
image:
|
||||
path: /illustrations/dark-mode
|
||||
width: 444
|
||||
height: 160
|
||||
class: 'col-span-5 row-span-5 lg:-mt-10 lg:mb-20'
|
||||
orientation: 'vertical'
|
||||
- title: Keyboard Shortcuts
|
||||
@@ -44,10 +56,13 @@ sections:
|
||||
icon: i-heroicons-computer-desktop
|
||||
to: /getting-started/shortcuts
|
||||
class: 'col-span-7 row-span-3'
|
||||
image: /illustrations/keyboard-shortcuts
|
||||
image:
|
||||
path: /illustrations/keyboard-shortcuts
|
||||
width: 444
|
||||
height: 160
|
||||
orientation: 'horizontal'
|
||||
links:
|
||||
- label: Learn more
|
||||
- label: Explore the docs
|
||||
to: /getting-started/theming
|
||||
color: white
|
||||
size: lg
|
||||
@@ -65,27 +80,33 @@ sections:
|
||||
categories:
|
||||
- label: Elements
|
||||
to: /elements/dropdown
|
||||
image: /illustrations/elements
|
||||
image:
|
||||
path: /illustrations/elements
|
||||
badge: 9
|
||||
- label: Forms
|
||||
to: /forms/form
|
||||
image: /illustrations/forms
|
||||
image:
|
||||
path: /illustrations/forms
|
||||
badge: 10
|
||||
- label: Data
|
||||
to: /data/table
|
||||
image: /illustrations/data
|
||||
image:
|
||||
path: /illustrations/data
|
||||
badge: 1
|
||||
- label: Navigation
|
||||
to: /navigation/command-palette
|
||||
image: /illustrations/navigation
|
||||
image:
|
||||
path: /illustrations/navigation
|
||||
badge: 4
|
||||
- label: Overlays
|
||||
to: /overlays/modal
|
||||
image: /illustrations/overlays
|
||||
image:
|
||||
path: /illustrations/overlays
|
||||
badge: 6
|
||||
- label: Layout
|
||||
to: /layout/card
|
||||
image: /illustrations/layout
|
||||
image:
|
||||
path: /illustrations/layout
|
||||
badge: 3
|
||||
cta:
|
||||
title: Trusted and supported by our<br class="hidden lg:block"> amazing community
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</UContainer>
|
||||
|
||||
<ClientOnly>
|
||||
<UDocsSearch :files="files" :navigation="navigation" />
|
||||
<LazyUDocsSearch :files="files" :navigation="navigation" />
|
||||
</ClientOnly>
|
||||
|
||||
<UNotifications />
|
||||
@@ -20,8 +20,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NuxtError } from '#app'
|
||||
|
||||
const { prefix, removePrefixFromNavigation, removePrefixFromFiles } = useContentSource()
|
||||
import type { ParsedContent } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
useSeoMeta({
|
||||
title: 'Page not found',
|
||||
@@ -32,22 +31,18 @@ defineProps<{
|
||||
error: NuxtError
|
||||
}>()
|
||||
|
||||
const { data: navigation } = await useLazyAsyncData('navigation', () => fetchContentNavigation(), {
|
||||
default: () => [],
|
||||
transform: (navigation) => {
|
||||
navigation = navigation.find(link => link._path === prefix.value)?.children || []
|
||||
const { branch } = useContentSource()
|
||||
|
||||
return prefix.value === '/main' ? removePrefixFromNavigation(navigation) : navigation
|
||||
}
|
||||
})
|
||||
const { data: nav } = await useAsyncData('navigation', () => fetchContentNavigation())
|
||||
const { data: files } = useLazyFetch<ParsedContent[]>('/api/search.json', { default: () => [], server: false })
|
||||
|
||||
const { data: files } = await useLazyAsyncData('files', () => queryContent().where({ _type: 'markdown', navigation: { $ne: false } }).find(), {
|
||||
default: () => [],
|
||||
transform: (files) => {
|
||||
files = files.filter(file => file._path.startsWith(prefix.value))
|
||||
// Computed
|
||||
|
||||
return prefix.value === '/main' ? removePrefixFromFiles(files) : files
|
||||
}
|
||||
const navigation = computed(() => {
|
||||
const main = nav.value.filter(item => item._path !== '/dev')
|
||||
const dev = nav.value.find(item => item._path === '/dev')?.children
|
||||
|
||||
return branch.value?.name === 'dev' ? dev : main
|
||||
})
|
||||
|
||||
// Provide
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<UContainer>
|
||||
<UPage>
|
||||
<template #left>
|
||||
<UAside :links="anchors">
|
||||
<UAside>
|
||||
<BranchSelect />
|
||||
|
||||
<UNavigationTree :links="mapContentNavigation(navigation)" />
|
||||
@@ -19,23 +19,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const { mapContentNavigation } = useElementsHelpers()
|
||||
|
||||
const navigation = inject<NavItem[]>('navigation')
|
||||
|
||||
const anchors = [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open-solid',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
icon: 'i-simple-icons-stackblitz',
|
||||
to: 'https://stackblitz.com/edit/nuxt-ui?file=app.config.ts,app.vue',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-heroicons-rocket-launch-solid',
|
||||
to: 'https://github.com/nuxt/ui/releases',
|
||||
target: '_blank'
|
||||
}]
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@ import pkg from '../package.json'
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
export default defineNuxtConfig({
|
||||
extends: process.env.NUXT_ELEMENTS_PATH || '@nuxthq/elements',
|
||||
extends: process.env.NUXT_UI_PRO_PATH || '@nuxt/ui-pro',
|
||||
modules: [
|
||||
'@nuxt/content',
|
||||
'nuxt-og-image',
|
||||
@@ -18,8 +18,7 @@ export default defineNuxtConfig({
|
||||
'@nuxtjs/google-fonts',
|
||||
'@nuxtjs/plausible',
|
||||
'@vueuse/nuxt',
|
||||
'nuxt-component-meta',
|
||||
'nuxt-lodash'
|
||||
'nuxt-component-meta'
|
||||
],
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
@@ -33,14 +32,13 @@ export default defineNuxtConfig({
|
||||
},
|
||||
content: {
|
||||
sources: {
|
||||
// overwrite default source AKA `content` directory
|
||||
content: {
|
||||
dev: {
|
||||
prefix: '/dev',
|
||||
driver: 'fs',
|
||||
base: resolve('./content')
|
||||
},
|
||||
main: {
|
||||
prefix: '/main',
|
||||
// overwrite default source AKA `content` directory
|
||||
content: {
|
||||
driver: 'github',
|
||||
repo: 'nuxt/ui',
|
||||
branch: 'main',
|
||||
@@ -70,7 +68,7 @@ export default defineNuxtConfig({
|
||||
},
|
||||
componentMeta: {
|
||||
globalsOnly: true,
|
||||
exclude: ['@nuxtjs/mdc', resolve('./components'), resolve('@nuxthq/elements/components')],
|
||||
exclude: ['@nuxtjs/mdc', resolve('./components'), resolve('@nuxt/ui-pro/components')],
|
||||
metaFields: {
|
||||
props: true,
|
||||
slots: false,
|
||||
@@ -78,10 +76,6 @@ export default defineNuxtConfig({
|
||||
exposed: false
|
||||
}
|
||||
},
|
||||
typescript: {
|
||||
strict: false,
|
||||
includeWorkspace: true
|
||||
},
|
||||
hooks: {
|
||||
// Related to https://github.com/nuxt/nuxt/pull/22558
|
||||
'components:extend': (components) => {
|
||||
|
||||
@@ -5,27 +5,27 @@
|
||||
"@nuxt/ui": "workspace:latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/heroicons": "latest",
|
||||
"@iconify-json/simple-icons": "latest",
|
||||
"@nuxt/content": "^2.8.2",
|
||||
"@nuxt/devtools": "^0.8.2",
|
||||
"@iconify-json/heroicons": "^1.1.12",
|
||||
"@iconify-json/simple-icons": "^1.1.73",
|
||||
"@nuxt/content": "^2.8.5",
|
||||
"@nuxt/devtools": "^0.8.5",
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxthq/elements": "npm:@nuxthq/elements-edge@0.0.1-28234841.b091775",
|
||||
"@nuxthq/studio": "^0.13.4",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.0.1-28265230.40bb224",
|
||||
"@nuxthq/studio": "^0.14.1",
|
||||
"@nuxtjs/fontaine": "^0.4.1",
|
||||
"@nuxtjs/google-fonts": "^3.0.2",
|
||||
"@nuxtjs/plausible": "^0.2.1",
|
||||
"@nuxtjs/plausible": "^0.2.3",
|
||||
"@vueuse/nuxt": "^10.4.1",
|
||||
"eslint": "^8.48.0",
|
||||
"joi": "^17.10.1",
|
||||
"nuxt": "^3.7.1",
|
||||
"nuxt-component-meta": "^0.5.3",
|
||||
"nuxt-lodash": "^2.5.0",
|
||||
"nuxt-og-image": "^2.0.25",
|
||||
"eslint": "^8.50.0",
|
||||
"joi": "^17.10.2",
|
||||
"nuxt": "^3.7.4",
|
||||
"nuxt-component-meta": "^0.5.4",
|
||||
"nuxt-og-image": "^2.0.28",
|
||||
"typescript": "^5.2.2",
|
||||
"ufo": "^1.3.0",
|
||||
"v-calendar": "^3.0.3",
|
||||
"yup": "^1.2.0",
|
||||
"ufo": "^1.3.1",
|
||||
"v-calendar": "^3.1.0",
|
||||
"valibot": "^0.17.1",
|
||||
"yup": "^1.3.1",
|
||||
"zod": "^3.22.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<UDivider v-if="surround?.length" />
|
||||
|
||||
<UDocsSurround :surround="removePrefixFromFiles(surround)" />
|
||||
<UDocsSurround :surround="(surround as ParsedContent[])" />
|
||||
</UPageBody>
|
||||
|
||||
<template v-if="page.body?.toc?.links?.length" #right>
|
||||
@@ -25,25 +25,34 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import type { ParsedContent } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const route = useRoute()
|
||||
const { prefix, removePrefixFromFiles } = useContentSource()
|
||||
const { findPageHeadline } = useElementsHelpers()
|
||||
const { branch } = useContentSource()
|
||||
|
||||
definePageMeta({
|
||||
layout: 'docs'
|
||||
})
|
||||
|
||||
const path = computed(() => route.path.startsWith(prefix.value) ? route.path : `${prefix.value}${route.path}`)
|
||||
|
||||
const { data: page } = await useAsyncData(path.value, () => queryContent(path.value).findOne())
|
||||
const { data: page } = await useAsyncData(route.path, () => queryContent(route.path).findOne())
|
||||
if (!page.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page not found' })
|
||||
}
|
||||
|
||||
const { data: surround } = await useAsyncData(`${path.value}-surround`, () => {
|
||||
return queryContent(prefix.value)
|
||||
.where({ _extension: 'md', navigation: { $ne: false } })
|
||||
.findSurround((path.value.endsWith('/') ? path.value.slice(0, -1) : path.value))
|
||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
||||
return queryContent()
|
||||
.where({
|
||||
_extension: 'md',
|
||||
_path: {
|
||||
[branch.value?.name === 'dev' ? '$eq' : '$ne']: new RegExp('^/dev')
|
||||
},
|
||||
navigation: {
|
||||
$ne: false
|
||||
}
|
||||
})
|
||||
.only(['title', 'description', '_path'])
|
||||
.findSurround(withoutTrailingSlash(route.path))
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
@@ -65,17 +74,27 @@ const headline = computed(() => findPageHeadline(page.value))
|
||||
const links = computed(() => [{
|
||||
icon: 'i-heroicons-pencil-square',
|
||||
label: 'Edit this page',
|
||||
to: `https://github.com/nuxt/ui/edit/dev/docs/content/${page?.value?._file}`,
|
||||
to: `https://github.com/nuxt/ui/edit/dev/docs/content/${branch.value?.name === 'dev' ? page?.value?._file.split('/').slice(1).join('/') : page?.value?._file}`,
|
||||
target: '_blank'
|
||||
}, {
|
||||
icon: 'i-heroicons-star',
|
||||
label: 'Star on GitHub',
|
||||
to: 'https://github.com/nuxt/ui',
|
||||
target: '_blank'
|
||||
}, {
|
||||
icon: 'i-heroicons-chat-bubble-bottom-center-text',
|
||||
label: 'Chat on Discord',
|
||||
to: 'https://discord.com/channels/473401852243869706/1153996761426300948',
|
||||
target: '_blank'
|
||||
}, {
|
||||
icon: 'i-heroicons-book-open',
|
||||
label: 'Nuxt documentation',
|
||||
label: 'Nuxt docs',
|
||||
to: 'https://nuxt.com',
|
||||
target: '_blank'
|
||||
}, {
|
||||
icon: 'i-simple-icons-figma',
|
||||
label: 'Figma Kit',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934/nuxt-ui',
|
||||
target: '_blank'
|
||||
}])
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div>
|
||||
<ULandingHero v-bind="page.hero" :ui="{ base: 'relative z-[1]' }" class="mb-[calc(var(--header-height)*2)]">
|
||||
<ULandingHero v-bind="page.hero" :ui="{ base: 'relative z-[1]', container: 'max-w-3xl' }" class="mb-[calc(var(--header-height)*2)]">
|
||||
<template #title>
|
||||
<span v-html="page.hero?.title" />
|
||||
</template>
|
||||
@@ -19,6 +20,7 @@
|
||||
autocomplete="off"
|
||||
icon="i-heroicons-command-line"
|
||||
input-class="select-none"
|
||||
aria-label="Install @nuxt/ui"
|
||||
size="lg"
|
||||
:ui="{ base: 'disabled:cursor-default', icon: { trailing: { pointer: '' } } }"
|
||||
>
|
||||
@@ -71,7 +73,14 @@
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div v-if="feature.image">
|
||||
<UColorModeImage :light="`${feature.image}-light.svg`" :dark="`${feature.image}-dark.svg`" class="object-cover w-full" />
|
||||
<UColorModeImage
|
||||
:light="`${feature.image.path}-light.svg`"
|
||||
:dark="`${feature.image.path}-dark.svg`"
|
||||
:width="feature.image.width"
|
||||
:height="feature.image.height"
|
||||
:alt="feature.title"
|
||||
class="object-cover w-full"
|
||||
/>
|
||||
</div>
|
||||
</ULandingCard>
|
||||
</ULandingGrid>
|
||||
@@ -85,7 +94,14 @@
|
||||
:to="category.to"
|
||||
class="hover:bg-gradient-to-b hover:from-gray-200/50 dark:hover:from-gray-800/50 rounded-lg"
|
||||
>
|
||||
<UColorModeImage :light="`${category.image}-light.svg`" :dark="`${category.image}-dark.svg`" class="object-cover w-full" />
|
||||
<UColorModeImage
|
||||
:light="`${category.image.path}-light.svg`"
|
||||
:dark="`${category.image.path}-dark.svg`"
|
||||
width="363"
|
||||
height="190"
|
||||
:alt="category.label"
|
||||
class="object-cover w-full"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-center gap-2 mt-1 mb-2">
|
||||
<span class="font-semibold text-lg">{{ category.label }}</span>
|
||||
@@ -116,28 +132,33 @@
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UAvatarGroup :max="xlAndLarger ? 13 : lgAndLarger ? 10 : mdAndLarger ? 16 : 8" size="md" class="flex-wrap-reverse [&_span:first-child]:text-xs justify-center">
|
||||
<UTooltip
|
||||
v-for="(contributor, index) of module.contributors"
|
||||
:key="index"
|
||||
:text="contributor.username"
|
||||
class="rounded-full"
|
||||
:ui="{ background: 'bg-gray-50 dark:bg-gray-800/50' }"
|
||||
:popper="{ offsetDistance: 16 }"
|
||||
>
|
||||
<UAvatar
|
||||
:alt="contributor.username"
|
||||
:src="`https://ipx.nuxt.com/s_40x40/gh_avatar/${contributor.username}`"
|
||||
:srcset="`https://ipx.nuxt.com/s_80x80/gh_avatar/${contributor.username} 2x`"
|
||||
class="lg:hover:scale-125 lg:hover:ring-2 lg:hover:ring-primary-500 dark:lg:hover:ring-primary-400 transition-transform"
|
||||
size="md"
|
||||
<ClientOnly>
|
||||
<UAvatarGroup :max="xlAndLarger ? 13 : lgAndLarger ? 10 : mdAndLarger ? 16 : 8" size="md" class="flex-wrap-reverse [&_span:first-child]:text-xs justify-center">
|
||||
<UTooltip
|
||||
v-for="(contributor, index) of module.contributors"
|
||||
:key="index"
|
||||
:text="contributor.username"
|
||||
class="rounded-full"
|
||||
:ui="{ background: 'bg-gray-50 dark:bg-gray-800/50' }"
|
||||
:popper="{ offsetDistance: 16 }"
|
||||
>
|
||||
<NuxtLink :to="`https://github.com/${contributor.username}`" target="_blank" class="focus:outline-none" tabindex="-1">
|
||||
<span class="absolute inset-0" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
</UAvatar>
|
||||
</UTooltip>
|
||||
</UAvatarGroup>
|
||||
<UAvatar
|
||||
:alt="contributor.username"
|
||||
:src="`https://ipx.nuxt.com/s_40x40/gh_avatar/${contributor.username}`"
|
||||
:srcset="`https://ipx.nuxt.com/s_80x80/gh_avatar/${contributor.username} 2x`"
|
||||
class="lg:hover:scale-125 lg:hover:ring-2 lg:hover:ring-primary-500 dark:lg:hover:ring-primary-400 transition-transform"
|
||||
width="40"
|
||||
height="40"
|
||||
size="md"
|
||||
loading="lazy"
|
||||
>
|
||||
<NuxtLink :to="`https://github.com/${contributor.username}`" :aria-label="contributor.username" target="_blank" class="focus:outline-none" tabindex="-1">
|
||||
<span class="absolute inset-0" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
</UAvatar>
|
||||
</UTooltip>
|
||||
</UAvatarGroup>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-8 lg:gap-16">
|
||||
@@ -161,10 +182,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { pick } from 'lodash-es'
|
||||
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
|
||||
|
||||
const { data: page } = await useAsyncData('index', () => queryContent('/').findOne())
|
||||
const { data: page } = await useAsyncData('index', () => queryContent('/dev').findOne())
|
||||
const { data: module } = await useFetch<{
|
||||
stats: {
|
||||
downloads: number
|
||||
@@ -174,7 +194,7 @@ const { data: module } = await useFetch<{
|
||||
username: string
|
||||
}[]
|
||||
}>('https://api.nuxt.com/modules/ui', {
|
||||
transform: (module) => pick(module, ['stats', 'contributors'])
|
||||
transform: ({ stats, contributors }) => ({ stats, contributors })
|
||||
})
|
||||
|
||||
const source = ref('npm i @nuxt/ui')
|
||||
|
||||
24
docs/pages/playground.vue
Normal file
24
docs/pages/playground.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
const title = 'Playground'
|
||||
const description = 'Play online with our interactive Nuxt Image playground.'
|
||||
|
||||
useSeoMeta({
|
||||
title,
|
||||
ogTitle: 'Nuxt UI Playground',
|
||||
description
|
||||
})
|
||||
|
||||
defineOgImage({
|
||||
component: 'Docs',
|
||||
title,
|
||||
description
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[calc(100vh-var(--header-height))]">
|
||||
<ClientOnly>
|
||||
<iframe :src="`https://stackblitz.com/edit/nuxt-ui?embed=1&file=app.config.ts,app.vue&theme=${$colorMode.preference}`" width="100%" height="100%" />
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,15 +1,3 @@
|
||||
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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" />
|
||||
<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="#00DC82" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1 +1,2 @@
|
||||
Disallow: /dev/*
|
||||
user-agent: *
|
||||
disallow: /dev/*
|
||||
|
||||
@@ -2,11 +2,6 @@ import type { Config } from 'tailwindcss'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
|
||||
export default <Partial<Config>>{
|
||||
content: {
|
||||
files: [
|
||||
'content/**/*.yml'
|
||||
]
|
||||
},
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
|
||||
40
package.json
40
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"version": "2.8.0",
|
||||
"version": "2.9.0",
|
||||
"repository": "https://github.com/nuxt/ui",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
@@ -25,15 +25,16 @@
|
||||
"build:docs": "nuxi generate docs",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"typecheck": "nuxi typecheck",
|
||||
"prepare": "nuxi prepare docs",
|
||||
"typecheck": "vue-tsc --noEmit && nuxi typecheck docs",
|
||||
"dev:prepare": "nuxt-module-build --stub && nuxt-module-build prepare && nuxi prepare docs",
|
||||
"release": "release-it"
|
||||
},
|
||||
"dependencies": {
|
||||
"@egoist/tailwindcss-icons": "^1.1.0",
|
||||
"@egoist/tailwindcss-icons": "^1.2.0",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@headlessui/vue": "^1.7.16",
|
||||
"@iconify-json/heroicons": "^1.1.12",
|
||||
"@nuxt/kit": "^3.7.1",
|
||||
"@nuxt/kit": "^3.7.4",
|
||||
"@nuxtjs/color-mode": "^3.3.0",
|
||||
"@nuxtjs/tailwindcss": "^6.8.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
@@ -46,28 +47,25 @@
|
||||
"@vueuse/math": "^10.4.1",
|
||||
"defu": "^6.1.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"ohash": "^1.1.3",
|
||||
"pathe": "^1.1.1",
|
||||
"scule": "^1.0.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxt/module-builder": "^0.5.1",
|
||||
"@release-it/conventional-changelog": "^7.0.1",
|
||||
"eslint": "^8.48.0",
|
||||
"joi": "^17.10.1",
|
||||
"nuxt": "^3.7.1",
|
||||
"release-it": "^16.1.5",
|
||||
"@nuxt/module-builder": "^0.5.2",
|
||||
"@release-it/conventional-changelog": "^7.0.2",
|
||||
"eslint": "^8.50.0",
|
||||
"joi": "^17.10.2",
|
||||
"nuxt": "^3.7.4",
|
||||
"release-it": "^16.2.1",
|
||||
"typescript": "^5.2.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"vue-tsc": "^1.8.10",
|
||||
"yup": "^1.2.0",
|
||||
"zod": "^3.22.2",
|
||||
"valibot": "^0.13.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"nuxt-component-meta@0.5.3": "patches/nuxt-component-meta@0.5.3.patch"
|
||||
}
|
||||
"valibot": "^0.17.1",
|
||||
"vue-tsc": "^1.8.15",
|
||||
"yup": "^1.3.1",
|
||||
"zod": "^3.22.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
diff --git a/dist/module.mjs b/dist/module.mjs
|
||||
index 286319046560f8ead5a26f811e7643fe990f9ee6..14be2f9551c24cd1e5c35dadaad9f83009f4c5a0 100644
|
||||
--- a/dist/module.mjs
|
||||
+++ b/dist/module.mjs
|
||||
@@ -142,10 +142,6 @@ const module = defineNuxtModule({
|
||||
references.push({
|
||||
path: join(nuxt.options.buildDir, "component-meta.d.ts")
|
||||
});
|
||||
- tsConfig.compilerOptions = tsConfig.compilerOptions || {};
|
||||
- tsConfig.compilerOptions.paths = tsConfig.compilerOptions.paths || {};
|
||||
- tsConfig.compilerOptions.paths["#nuxt-component-meta"] = [withoutLeadingSlash(join(nuxt.options.buildDir, "/component-meta.mjs").replace(nuxt.options.rootDir, ""))];
|
||||
- tsConfig.compilerOptions.paths["#nuxt-component-meta/types"] = [withoutLeadingSlash(join(nuxt.options.buildDir, "/component-meta.d.ts").replace(nuxt.options.rootDir, ""))];
|
||||
});
|
||||
nuxt.hook("nitro:config", (nitroConfig) => {
|
||||
nitroConfig.handlers = nitroConfig.handlers || [];
|
||||
1
playground/.nuxtrc
Normal file
1
playground/.nuxtrc
Normal file
@@ -0,0 +1 @@
|
||||
imports.autoImport=true
|
||||
3376
pnpm-lock.yaml
generated
3376
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,6 @@ git restore -s@ -SW -- .
|
||||
# Bump versions to edge
|
||||
pnpm jiti ./scripts/bump-edge
|
||||
|
||||
# Resolve pnpm
|
||||
pnpm install
|
||||
|
||||
# Update token
|
||||
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
|
||||
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
# Restore all git changes
|
||||
git restore -s@ -SW -- .
|
||||
|
||||
# Resolve pnpm
|
||||
pnpm install
|
||||
|
||||
# Update token
|
||||
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
|
||||
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { omit, kebabCase, camelCase, upperFirst } from 'lodash-es'
|
||||
import { omit } from './runtime/utils/lodash'
|
||||
import { kebabCase, camelCase, upperFirst } from 'scule'
|
||||
|
||||
const colorsToExclude = [
|
||||
'inherit',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin, resolvePath } from '@nuxt/kit'
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
|
||||
import defaultColors from 'tailwindcss/colors.js'
|
||||
import { defaultExtractor as createDefaultExtractor } from 'tailwindcss/lib/lib/defaultExtractor.js'
|
||||
import { iconsPlugin, getIconCollections } from '@egoist/tailwindcss-icons'
|
||||
import { name, version } from '../package.json'
|
||||
import { generateSafelist, excludeColors, customSafelistExtractor } from './colors'
|
||||
import appConfig from './runtime/app.config'
|
||||
|
||||
type DeepPartial<T> = Partial<{ [P in keyof T]: DeepPartial<T[P]> | { [key: string]: string } }>
|
||||
import createTemplates from './templates'
|
||||
import * as config from './runtime/ui.config'
|
||||
import type { DeepPartial, Strategy } from './runtime/types/utils'
|
||||
|
||||
const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } })
|
||||
|
||||
@@ -16,13 +16,22 @@ delete defaultColors.trueGray
|
||||
delete defaultColors.coolGray
|
||||
delete defaultColors.blueGray
|
||||
|
||||
type UI = {
|
||||
primary?: string
|
||||
gray?: string
|
||||
colors?: string[]
|
||||
strategy?: Strategy
|
||||
[key: string]: any
|
||||
} & DeepPartial<typeof config>
|
||||
|
||||
declare module 'nuxt/schema' {
|
||||
interface AppConfigInput {
|
||||
ui?: UI
|
||||
}
|
||||
}
|
||||
declare module '@nuxt/schema' {
|
||||
interface AppConfigInput {
|
||||
ui?: {
|
||||
primary?: string
|
||||
gray?: string
|
||||
colors?: string[]
|
||||
} & DeepPartial<typeof appConfig.ui>
|
||||
ui?: UI
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +73,9 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
nuxt.options.build.transpile.push(runtimeDir)
|
||||
nuxt.options.build.transpile.push('@popperjs/core', '@headlessui/vue')
|
||||
|
||||
nuxt.options.css.push(resolve(runtimeDir, 'ui.css'))
|
||||
nuxt.options.alias['#ui'] = runtimeDir
|
||||
|
||||
const appConfigFile = await resolvePath(resolve(runtimeDir, 'app.config'))
|
||||
nuxt.hook('app:resolve', (app) => {
|
||||
app.configs.push(appConfigFile)
|
||||
})
|
||||
nuxt.options.css.push(resolve(runtimeDir, 'ui.css'))
|
||||
|
||||
nuxt.hook('tailwindcss:config', function (tailwindConfig) {
|
||||
const globalColors: any = {
|
||||
@@ -116,11 +122,12 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
|
||||
const colors = excludeColors(globalColors)
|
||||
|
||||
// @ts-ignore
|
||||
nuxt.options.appConfig.ui = {
|
||||
...nuxt.options.appConfig.ui,
|
||||
primary: 'green',
|
||||
gray: 'cool',
|
||||
colors
|
||||
colors,
|
||||
strategy: 'merge'
|
||||
}
|
||||
|
||||
tailwindConfig.safelist = tailwindConfig.safelist || []
|
||||
@@ -130,6 +137,8 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
tailwindConfig.plugins.push(iconsPlugin({ collections: getIconCollections(options.icons as any[]) }))
|
||||
})
|
||||
|
||||
createTemplates(nuxt)
|
||||
|
||||
// Modules
|
||||
|
||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||
@@ -141,7 +150,8 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
require('@tailwindcss/forms')({ strategy: 'class' }),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/container-queries')
|
||||
require('@tailwindcss/container-queries'),
|
||||
require('@headlessui/tailwindcss')
|
||||
],
|
||||
content: {
|
||||
files: [
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div :class="wrapperClass" v-bind="attrs">
|
||||
<div :class="ui.wrapper" v-bind="attrs">
|
||||
<table :class="[ui.base, ui.divide]">
|
||||
<thead :class="ui.thead">
|
||||
<tr :class="ui.tr.base">
|
||||
<th v-if="modelValue" scope="col" class="ps-4">
|
||||
<UCheckbox :checked="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" @change="onChange" />
|
||||
<th v-if="modelValue" scope="col" :class="ui.checkbox.padding">
|
||||
<UCheckbox :checked="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" aria-label="Select all" @change="onChange" />
|
||||
</th>
|
||||
|
||||
<th v-for="(column, index) in columns" :key="index" scope="col" :class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.class]">
|
||||
@@ -49,12 +49,12 @@
|
||||
</tr>
|
||||
|
||||
<template v-else>
|
||||
<tr v-for="(row, index) in rows" :key="index" :class="[ui.tr.base, isSelected(row) && ui.tr.selected, $attrs.onSelect && ui.tr.active]" @click="() => onSelect(row)">
|
||||
<td v-if="modelValue" class="ps-4">
|
||||
<UCheckbox v-model="selected" :value="row" @click.stop />
|
||||
<tr v-for="(row, index) in rows" :key="index" :class="[ui.tr.base, isSelected(row) && ui.tr.selected, $attrs.onSelect && ui.tr.active, row?.class]" @click="() => onSelect(row)">
|
||||
<td v-if="modelValue" :class="ui.checkbox.padding">
|
||||
<UCheckbox v-model="selected" :value="row" aria-label="Select row" @click.stop />
|
||||
</td>
|
||||
|
||||
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size]">
|
||||
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, row[column.key]?.class]">
|
||||
<slot :name="`${column.key}-data`" :column="column" :row="row" :index="index" :get-row-data="(defaultValue) => getRowData(row, column.key, defaultValue)">
|
||||
{{ getRowData(row, column.key) }}
|
||||
</slot>
|
||||
@@ -67,22 +67,21 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, defineComponent, toRaw } from 'vue'
|
||||
import { ref, computed, defineComponent, toRaw, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit, capitalize, orderBy, get } from 'lodash-es'
|
||||
import { upperFirst } from 'scule'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UCheckbox from '../forms/Checkbox.vue'
|
||||
import type { Button } from '../../types/button'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, omit, get } from '../../utils'
|
||||
import type { Strategy, Button } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { table } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof table>(appConfig.ui.strategy, appConfig.ui.table, table)
|
||||
|
||||
function defaultComparator<T> (a: T, z: T): boolean {
|
||||
return a === z
|
||||
@@ -122,15 +121,15 @@ export default defineComponent({
|
||||
},
|
||||
sortButton: {
|
||||
type: Object as PropType<Button>,
|
||||
default: () => appConfig.ui.table.default.sortButton
|
||||
default: () => config.default.sortButton as Button
|
||||
},
|
||||
sortAscIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.table.default.sortAscIcon
|
||||
default: () => config.default.sortAscIcon
|
||||
},
|
||||
sortDescIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.table.default.sortDescIcon
|
||||
default: () => config.default.sortDescIcon
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
@@ -138,27 +137,26 @@ export default defineComponent({
|
||||
},
|
||||
loadingState: {
|
||||
type: Object as PropType<{ icon: string, label: string }>,
|
||||
default: () => appConfig.ui.table.default.loadingState
|
||||
default: () => config.default.loadingState
|
||||
},
|
||||
emptyState: {
|
||||
type: Object as PropType<{ icon: string, label: string }>,
|
||||
default: () => appConfig.ui.table.default.emptyState
|
||||
default: () => config.default.emptyState
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.table>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup (props, { emit, attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
setup (props, { emit, attrs: $attrs }) {
|
||||
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.table>>(() => defuTwMerge({}, props.ui, appConfig.ui.table))
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
const columns = computed(() => props.columns ?? Object.keys(omit(props.rows[0] ?? {}, ['click'])).map((key) => ({ key, label: capitalize(key), sortable: false })))
|
||||
const columns = computed(() => props.columns ?? Object.keys(omit(props.rows[0] ?? {}, ['click'])).map((key) => ({ key, label: upperFirst(key), sortable: false })))
|
||||
|
||||
const sort = ref(defu({}, props.sort, { column: null, direction: 'asc' }))
|
||||
|
||||
@@ -169,7 +167,20 @@ export default defineComponent({
|
||||
|
||||
const { column, direction } = sort.value
|
||||
|
||||
return orderBy(props.rows, column, direction)
|
||||
return props.rows.slice().sort((a, b) => {
|
||||
const aValue = a[column]
|
||||
const bValue = b[column]
|
||||
|
||||
if (aValue === bValue) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (direction === 'asc') {
|
||||
return aValue < bValue ? -1 : 1
|
||||
} else {
|
||||
return aValue > bValue ? -1 : 1
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const selected = computed({
|
||||
@@ -224,12 +235,12 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function onSelect (row) {
|
||||
if (!attrs.onSelect) {
|
||||
if (!$attrs.onSelect) {
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
attrs.onSelect(row)
|
||||
$attrs.onSelect(row)
|
||||
}
|
||||
|
||||
function selectAllRows () {
|
||||
@@ -239,7 +250,8 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
|
||||
onSelect(row)
|
||||
// @ts-ignore
|
||||
$attrs.onSelect ? $attrs.onSelect(row) : selected.value.push(row)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -256,10 +268,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
wrapperClass,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
sort,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div :class="ui.wrapper">
|
||||
<HDisclosure v-for="(item, index) in items" v-slot="{ open, close }" :key="index" :default-open="defaultOpen || item.defaultOpen">
|
||||
<HDisclosureButton :ref="() => buttonRefs[index] = close" as="template" :disabled="item.disabled">
|
||||
<slot :item="item" :index="index" :open="open" :close="close">
|
||||
@@ -40,20 +40,22 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, defineComponent } from 'vue'
|
||||
import { ref, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Disclosure as HDisclosure, DisclosureButton as HDisclosureButton, DisclosurePanel as HDisclosurePanel } from '@headlessui/vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, omit } from '../../utils'
|
||||
import StateEmitter from '../../utils/StateEmitter'
|
||||
import type { AccordionItem } from '../../types/accordion'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import type { AccordionItem, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { accordion, button } from '#ui/ui.config'
|
||||
|
||||
const config = mergeConfig<typeof accordion>(appConfig.ui.strategy, appConfig.ui.accordion, accordion)
|
||||
|
||||
const configButton = mergeConfig<typeof button>(appConfig.ui.strategy, appConfig.ui.button, button)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -76,43 +78,47 @@ export default defineComponent({
|
||||
},
|
||||
openIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.accordion.default.openIcon
|
||||
default: () => config.default.openIcon
|
||||
},
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.accordion.default.closeIcon
|
||||
default: () => config.default.closeIcon
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.accordion>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
setup (props) {
|
||||
const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.accordion>>(() => defuTwMerge({}, props.ui, appConfig.ui.accordion))
|
||||
|
||||
const uiButton = computed<Partial<typeof appConfig.ui.button>>(() => appConfig.ui.button)
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
const uiButton = computed<Partial<typeof configButton>>(() => configButton)
|
||||
|
||||
const buttonRefs = ref<Function[]>([])
|
||||
|
||||
function closeOthers (itemIndex: number) {
|
||||
if (!props.items[itemIndex].closeOthers && props.multiple) {
|
||||
function closeOthers (currentIndex: number) {
|
||||
if (!props.items[currentIndex].closeOthers && props.multiple) {
|
||||
return
|
||||
}
|
||||
|
||||
buttonRefs.value.forEach((close, index) => {
|
||||
if (index === itemIndex) return
|
||||
const totalItems = buttonRefs.value.length
|
||||
|
||||
const order = Array.from({ length: totalItems }, (_, i) => (currentIndex + i) % totalItems)
|
||||
.filter(index => index !== currentIndex)
|
||||
.reverse()
|
||||
|
||||
for (const index of order) {
|
||||
const close = buttonRefs.value[index]
|
||||
close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onEnter (el: HTMLElement, done) {
|
||||
@@ -139,11 +145,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
uiButton,
|
||||
wrapperClass,
|
||||
attrs,
|
||||
buttonRefs,
|
||||
closeOthers,
|
||||
omit,
|
||||
|
||||
@@ -25,29 +25,28 @@
|
||||
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...ui.default.actionButton, ...action }" @click.stop="action.click" />
|
||||
</div>
|
||||
|
||||
<UButton v-if="closeButton" v-bind="{ ...ui.default.closeButton, ...closeButton }" @click.stop="$emit('close')" />
|
||||
<UButton v-if="closeButton" aria-label="Close" v-bind="{ ...ui.default.closeButton, ...closeButton }" @click.stop="$emit('close')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import type { Avatar } from '../../types/avatar'
|
||||
import type { Button } from '../../types/button'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import type { Avatar, Button, NestedKeyOf, Strategy } from '../../types'
|
||||
import { mergeConfig } from '../../utils'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { omit } from 'lodash-es'
|
||||
import { alert } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof alert>(appConfig.ui.strategy, appConfig.ui.alert, alert)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -67,7 +66,7 @@ export default defineComponent({
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.alert.default.icon
|
||||
default: () => config.default.icon
|
||||
},
|
||||
avatar: {
|
||||
type: Object as PropType<Avatar>,
|
||||
@@ -75,40 +74,41 @@ export default defineComponent({
|
||||
},
|
||||
closeButton: {
|
||||
type: Object as PropType<Button>,
|
||||
default: () => appConfig.ui.alert.default.closeButton
|
||||
default: () => config.default.closeButton as Button
|
||||
},
|
||||
actions: {
|
||||
type: Array as PropType<(Button & { click?: Function })[]>,
|
||||
default: () => []
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.alert.default.color,
|
||||
type: String as PropType<keyof typeof config.color | typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.alert.color)].includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.alert.default.variant,
|
||||
type: String as PropType<keyof typeof config.variant | NestedKeyOf<typeof config.color>>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.alert.variant),
|
||||
...Object.values(appConfig.ui.alert.color).flatMap(value => Object.keys(value))
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.alert>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
setup (props, { attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.alert>>(() => defuTwMerge({}, props.ui, appConfig.ui.alert))
|
||||
setup (props) {
|
||||
const { ui, attrs } = useUI('alert', toRef(props, 'ui'), config)
|
||||
|
||||
const alertClass = computed(() => {
|
||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
@@ -119,13 +119,13 @@ export default defineComponent({
|
||||
ui.value.shadow,
|
||||
ui.value.padding,
|
||||
variant?.replaceAll('{color}', props.color)
|
||||
), attrs.class as string)
|
||||
), props.class)
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
alertClass
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch } from 'vue'
|
||||
import { defineComponent, ref, computed, toRef, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { AvatarSize, AvatarChipColor, AvatarChipPosition, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { omit } from 'lodash-es'
|
||||
import { avatar } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof avatar>(appConfig.ui.strategy, appConfig.ui.avatar, avatar)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -53,27 +53,27 @@ export default defineComponent({
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.avatar.default.icon
|
||||
default: () => config.default.icon
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.avatar.default.size,
|
||||
type: String as PropType<AvatarSize>,
|
||||
default: () => config.default.size,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.avatar.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
chipColor: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.avatar.default.chipColor,
|
||||
type: String as PropType<AvatarChipColor>,
|
||||
default: () => config.default.chipColor,
|
||||
validator (value: string) {
|
||||
return ['gray', ...appConfig.ui.colors].includes(value)
|
||||
}
|
||||
},
|
||||
chipPosition: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.avatar.default.chipPosition,
|
||||
type: String as PropType<AvatarChipPosition>,
|
||||
default: () => config.default.chipPosition,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.avatar.chip.position).includes(value)
|
||||
return Object.keys(config.chip.position).includes(value)
|
||||
}
|
||||
},
|
||||
chipText: {
|
||||
@@ -84,16 +84,17 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.avatar>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.avatar>>(() => defuTwMerge({}, props.ui, appConfig.ui.avatar))
|
||||
setup (props) {
|
||||
const { ui, attrs } = useUI('avatar', toRef(props, 'ui'), config)
|
||||
|
||||
const url = computed(() => {
|
||||
if (typeof props.src === 'boolean') {
|
||||
@@ -112,7 +113,7 @@ export default defineComponent({
|
||||
(error.value || !url.value) && ui.value.background,
|
||||
ui.value.rounded,
|
||||
ui.value.size[props.size]
|
||||
), attrs.class as string)
|
||||
), props.class)
|
||||
})
|
||||
|
||||
const imgClass = computed(() => {
|
||||
@@ -151,7 +152,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
imgClass,
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
import { h, cloneVNode, computed, defineComponent } from 'vue'
|
||||
import { h, cloneVNode, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge, getSlotsChildren } from '../../utils'
|
||||
import Avatar from './Avatar.vue'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import UAvatar from './Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getSlotsChildren } from '../../utils'
|
||||
import type { AvatarSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { avatar, avatarGroup } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const avatarConfig = mergeConfig<typeof avatar>(appConfig.ui.strategy, appConfig.ui.avatar, avatar)
|
||||
|
||||
const avatarGroupConfig = mergeConfig<typeof avatarGroup>(appConfig.ui.strategy, appConfig.ui.avatarGroup, avatarGroup)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
type: String as PropType<keyof typeof avatarConfig.size>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.avatar.size).includes(value)
|
||||
return Object.keys(avatarConfig.size).includes(value)
|
||||
}
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.avatarGroup>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof avatarGroupConfig & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs, slots }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.avatarGroup>>(() => defuTwMerge({}, props.ui, appConfig.ui.avatarGroup))
|
||||
setup (props, { slots }) {
|
||||
const { ui, attrs } = useUI('avatarGroup', toRef(props, 'ui'), avatarGroupConfig, toRef(props, 'class'))
|
||||
|
||||
const children = computed(() => getSlotsChildren(slots))
|
||||
|
||||
@@ -55,8 +58,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (max.value !== undefined && index === max.value) {
|
||||
return h(Avatar, {
|
||||
size: props.size || appConfig.ui.avatar.default.size,
|
||||
return h(UAvatar, {
|
||||
size: props.size || (avatarConfig.default.size as AvatarSize),
|
||||
text: `+${children.value.length - max.value}`,
|
||||
class: twJoin(ui.value.ring, ui.value.margin)
|
||||
})
|
||||
@@ -65,6 +68,6 @@ export default defineComponent({
|
||||
return null
|
||||
}).filter(Boolean).reverse())
|
||||
|
||||
return () => h('div', { class: twMerge(ui.value.wrapper, attrs.class as string), ...omit(attrs, ['class']) }, clones.value)
|
||||
return () => h('div', { class: ui.value.wrapper, ...attrs.value }, clones.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,42 +5,42 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { BadgeColor, BadgeSize, BadgeVariant, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { badge } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof badge>(appConfig.ui.strategy, appConfig.ui.badge, badge)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.badge.default.size,
|
||||
type: String as PropType<BadgeSize>,
|
||||
default: () => config.default.size,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.badge.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.badge.default.color,
|
||||
type: String as PropType<BadgeColor>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.badge.color)].includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.badge.default.variant,
|
||||
type: String as PropType<BadgeVariant>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.badge.variant),
|
||||
...Object.values(appConfig.ui.badge.color).flatMap(value => Object.keys(value))
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
@@ -48,16 +48,17 @@ export default defineComponent({
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.badge>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.badge>>(() => defuTwMerge({}, props.ui, appConfig.ui.badge))
|
||||
setup (props) {
|
||||
const { ui, attrs } = useUI('badge', toRef(props, 'ui'), config)
|
||||
|
||||
const badgeClass = computed(() => {
|
||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
@@ -68,11 +69,11 @@ export default defineComponent({
|
||||
ui.value.rounded,
|
||||
ui.value.size[props.size],
|
||||
variant?.replaceAll('{color}', props.color)
|
||||
), attrs.class as string)
|
||||
), props.class)
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
attrs,
|
||||
badgeClass
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, defineComponent, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { ButtonColor, ButtonSize, ButtonVariant, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { button } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof button>(appConfig.ui.strategy, appConfig.ui.button, button)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -63,26 +63,26 @@ export default defineComponent({
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.button.default.size,
|
||||
type: String as PropType<ButtonSize>,
|
||||
default: () => config.default.size,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.button.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.button.default.color,
|
||||
type: String as PropType<ButtonColor>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.button.color)].includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.button.default.variant,
|
||||
type: String as PropType<ButtonVariant>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.button.variant),
|
||||
...Object.values(appConfig.ui.button.color).flatMap(value => Object.keys(value))
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
@@ -92,7 +92,7 @@ export default defineComponent({
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.button.default.loadingIcon
|
||||
default: () => config.default.loadingIcon
|
||||
},
|
||||
leadingIcon: {
|
||||
type: String,
|
||||
@@ -118,16 +118,17 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.button>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs, slots }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.button>>(() => defuTwMerge({}, props.ui, appConfig.ui.button))
|
||||
setup (props, { slots }) {
|
||||
const { ui, attrs } = useUI('button', toRef(props, 'ui'), config)
|
||||
|
||||
const isLeading = computed(() => {
|
||||
return (props.icon && props.leading) || (props.icon && !props.trailing) || (props.loading && !props.trailing) || props.leadingIcon
|
||||
@@ -151,7 +152,7 @@ export default defineComponent({
|
||||
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'
|
||||
), attrs.class as string)
|
||||
), props.class)
|
||||
})
|
||||
|
||||
const leadingIconName = computed(() => {
|
||||
@@ -187,7 +188,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
attrs,
|
||||
isLeading,
|
||||
isTrailing,
|
||||
isSquare,
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { h, cloneVNode, computed, defineComponent } from 'vue'
|
||||
import { h, cloneVNode, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge, getSlotsChildren } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getSlotsChildren } from '../../utils'
|
||||
import type { ButtonSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { button, buttonGroup } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const buttonConfig = mergeConfig<typeof button>(appConfig.ui.strategy, appConfig.ui.button, button)
|
||||
const buttonGroupConfig = mergeConfig<typeof buttonGroup>(appConfig.ui.strategy, appConfig.ui.buttonGroup, buttonGroup)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
type: String as PropType<ButtonSize>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.button.size).includes(value)
|
||||
return Object.keys(buttonConfig.size).includes(value)
|
||||
}
|
||||
},
|
||||
orientation: {
|
||||
@@ -27,16 +28,17 @@ export default defineComponent({
|
||||
return ['horizontal', 'vertical'].includes(value)
|
||||
}
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.buttonGroup>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof buttonGroupConfig & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs, slots }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.buttonGroup>>(() => defuTwMerge({}, props.ui, appConfig.ui.buttonGroup))
|
||||
setup (props, { slots }) {
|
||||
const { ui, attrs } = useUI('buttonGroup', toRef(props, 'ui'), buttonGroupConfig)
|
||||
|
||||
const children = computed(() => getSlotsChildren(slots))
|
||||
|
||||
@@ -58,12 +60,6 @@ export default defineComponent({
|
||||
const clones = computed(() => children.value.map((node, index) => {
|
||||
const vProps: any = {}
|
||||
|
||||
if (props.orientation === 'vertical') {
|
||||
ui.value.wrapper = 'flex flex-col -space-y-px'
|
||||
} else {
|
||||
ui.value.wrapper = 'inline-flex -space-x-px'
|
||||
}
|
||||
|
||||
if (props.size) {
|
||||
vProps.size = props.size
|
||||
}
|
||||
@@ -83,6 +79,14 @@ export default defineComponent({
|
||||
return cloneVNode(node, vProps)
|
||||
}))
|
||||
|
||||
return () => h('div', { class: twMerge(twJoin(ui.value.wrapper, ui.value.rounded, ui.value.shadow), attrs.class as string), ...omit(attrs, ['class']) }, clones.value)
|
||||
const wrapperClass = computed(() => {
|
||||
return twMerge(twJoin(
|
||||
ui.value.wrapper[props.orientation],
|
||||
ui.value.rounded,
|
||||
ui.value.shadow
|
||||
), props.class)
|
||||
})
|
||||
|
||||
return () => h('div', { class: wrapperClass.value, ...attrs.value }, clones.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<HMenu v-slot="{ open }" as="div" :class="wrapperClass" v-bind="attrs" @mouseleave="onMouseLeave">
|
||||
<HMenu v-slot="{ open }" as="div" :class="ui.wrapper" v-bind="attrs" @mouseleave="onMouseLeave">
|
||||
<HMenuButton
|
||||
ref="trigger"
|
||||
as="div"
|
||||
@@ -45,26 +45,23 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, onMounted } from 'vue'
|
||||
import { defineComponent, ref, computed, toRef, onMounted } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem } from '@headlessui/vue'
|
||||
import { defu } from 'defu'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UKbd from '../elements/Kbd.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import type { DropdownItem } from '../../types/dropdown'
|
||||
import type { PopperOptions } from '../../types/popper'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig, omit } from '../../utils'
|
||||
import type { DropdownItem, PopperOptions, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { dropdown } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof dropdown>(appConfig.ui.strategy, appConfig.ui.dropdown, dropdown)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -104,16 +101,17 @@ export default defineComponent({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.dropdown>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.dropdown>>(() => defuTwMerge({}, props.ui, appConfig.ui.dropdown))
|
||||
setup (props) {
|
||||
const { ui, attrs } = useUI('dropdown', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu(props.mode === 'hover' ? { offsetDistance: 0 } : {}, props.popper, ui.value.popper as PopperOptions))
|
||||
|
||||
@@ -143,8 +141,6 @@ export default defineComponent({
|
||||
return props.mode === 'hover' ? { paddingTop: `${offsetDistance}px`, paddingBottom: `${offsetDistance}px` } : {}
|
||||
})
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
function onMouseOver () {
|
||||
if (props.mode !== 'hover' || !menuApi.value) {
|
||||
return
|
||||
@@ -186,13 +182,12 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
trigger,
|
||||
container,
|
||||
containerStyle,
|
||||
wrapperClass,
|
||||
onMouseOver,
|
||||
onMouseLeave,
|
||||
omit
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { toRef, defineComponent, computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { kbd } from '#ui/ui.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof kbd>(appConfig.ui.strategy, appConfig.ui.kbd, kbd)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
@@ -25,22 +25,23 @@ export default defineComponent({
|
||||
default: null
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.kbd.default.size,
|
||||
type: String as PropType<keyof typeof config.size>,
|
||||
default: () => config.default.size,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.kbd.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.kbd>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.kbd>>(() => defuTwMerge({}, props.ui, appConfig.ui.kbd))
|
||||
setup (props) {
|
||||
const { ui, attrs } = useUI('kbd', toRef(props, 'ui'), config)
|
||||
|
||||
const kbdClass = computed(() => {
|
||||
return twMerge(twJoin(
|
||||
@@ -51,13 +52,13 @@ export default defineComponent({
|
||||
ui.value.font,
|
||||
ui.value.background,
|
||||
ui.value.ring
|
||||
), attrs.class as string)
|
||||
), props.class)
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
kbdClass
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<button v-if="!to" :type="type" :disabled="disabled" v-bind="$attrs" :class="inactiveClass">
|
||||
<component
|
||||
:is="as"
|
||||
v-if="!to"
|
||||
:disabled="disabled"
|
||||
v-bind="$attrs"
|
||||
:class="inactiveClass"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</component>
|
||||
<NuxtLink
|
||||
v-else
|
||||
v-slot="{ route, href, target, rel, navigate, isActive, isExactActive, isExternal }"
|
||||
@@ -24,7 +30,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { isEqual } from 'ohash'
|
||||
import { defineComponent } from 'vue'
|
||||
import { NuxtLink } from '#components'
|
||||
|
||||
@@ -32,14 +38,18 @@ export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
...NuxtLink.props,
|
||||
type: {
|
||||
as: {
|
||||
type: String,
|
||||
default: null
|
||||
default: 'button'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
exact: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -59,6 +69,10 @@ export default defineComponent({
|
||||
},
|
||||
setup (props) {
|
||||
function resolveLinkClass (route, $route, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) {
|
||||
if (props.active) {
|
||||
return props.activeClass
|
||||
}
|
||||
|
||||
if (props.exactQuery && !isEqual(route.query, $route.query)) {
|
||||
return props.inactiveClass
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div :class="ui.wrapper">
|
||||
<div class="flex items-center h-5">
|
||||
<input
|
||||
:id="name"
|
||||
:id="inputId"
|
||||
v-model="toggle"
|
||||
:name="name"
|
||||
:required="required"
|
||||
@@ -18,7 +18,7 @@
|
||||
>
|
||||
</div>
|
||||
<div v-if="label || $slots.label" class="ms-3 text-sm">
|
||||
<label :for="name" :class="ui.label">
|
||||
<label :for="inputId" :class="ui.label">
|
||||
<slot name="label">{{ label }}</slot>
|
||||
<span v-if="required" :class="ui.required">*</span>
|
||||
</label>
|
||||
@@ -30,22 +30,29 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { uid } from '../../utils/uid'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { checkbox } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof checkbox>(appConfig.ui.strategy, appConfig.ui.checkbox, checkbox)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
// A default value is needed here to bind the label
|
||||
default: () => uid()
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean, Object],
|
||||
default: null
|
||||
@@ -83,8 +90,8 @@ export default defineComponent({
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.checkbox.default.color,
|
||||
type: String as PropType<typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return appConfig.ui.colors.includes(value)
|
||||
}
|
||||
@@ -93,20 +100,20 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.checkbox>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup (props, { emit, attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('checkbox', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.checkbox>>(() => defuTwMerge({}, props.ui, appConfig.ui.checkbox))
|
||||
|
||||
const { emitFormChange, formGroup } = useFormGroup()
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const { emitFormChange, color, name, inputId } = useFormGroup(props)
|
||||
|
||||
const toggle = computed({
|
||||
get () {
|
||||
@@ -122,8 +129,6 @@ export default defineComponent({
|
||||
emitFormChange()
|
||||
}
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
const inputClass = computed(() => {
|
||||
return twMerge(twJoin(
|
||||
ui.value.base,
|
||||
@@ -136,11 +141,13 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
toggle,
|
||||
wrapperClass,
|
||||
inputId,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
inputClass,
|
||||
onChange
|
||||
|
||||
@@ -10,9 +10,9 @@ import { useEventBus } from '@vueuse/core'
|
||||
import type { ZodSchema } from 'zod'
|
||||
import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
|
||||
import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup'
|
||||
import type { ObjectSchema as ValibotObjectSchema } from 'valibot'
|
||||
import { safeParseAsync } from 'valibot'
|
||||
import type { ObjectSchemaAsync as ValibotObjectSchema } from 'valibot'
|
||||
import type { FormError, FormEvent, FormEventType, FormSubmitEvent, Form } from '../../types/form'
|
||||
import { uid } from '../../utils/uid'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -41,8 +41,7 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['submit'],
|
||||
setup (props, { expose, emit }) {
|
||||
const seed = Math.random().toString(36).substring(7)
|
||||
const bus = useEventBus<FormEvent>(`form-${seed}`)
|
||||
const bus = useEventBus<FormEvent>(`form-${uid()}`)
|
||||
|
||||
bus.on(async (event) => {
|
||||
if (event.type !== 'submit' && props.validateOn?.includes(event.type)) {
|
||||
@@ -218,8 +217,8 @@ async function getValibotError (
|
||||
state: any,
|
||||
schema: ValibotObjectSchema<any>
|
||||
): Promise<FormError[]> {
|
||||
const result = await safeParseAsync(schema, state)
|
||||
if (result.success === false) {
|
||||
const result = await schema._parse(state)
|
||||
if (result.issues) {
|
||||
return result.issues.map((issue) => ({
|
||||
path: issue.path.map(p => p.key).join('.'),
|
||||
message: issue.message
|
||||
|
||||
@@ -1,36 +1,54 @@
|
||||
<template>
|
||||
<div :class="wrapperClass" v-bind="attrs">
|
||||
<label>
|
||||
<div v-if="label" :class="[ui.label.wrapper, size]">
|
||||
<p :class="[ui.label.base, required ? ui.label.required : '']">{{ label }}</p>
|
||||
<span v-if="hint" :class="[ui.hint]">{{ hint }}</span>
|
||||
</div>
|
||||
<div :class="ui.wrapper" v-bind="attrs">
|
||||
<div v-if="label || $slots.label" :class="[ui.label.wrapper, size]">
|
||||
<label :for="inputId" :class="[ui.label.base, required ? ui.label.required : '']">
|
||||
<slot v-if="$slots.label" name="label" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>{{ label }}</template>
|
||||
</label>
|
||||
<span v-if="hint || $slots.hint" :class="[ui.hint]">
|
||||
<slot v-if="$slots.hint" name="hint" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>{{ hint }}</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p v-if="description" :class="[ui.description, size]">{{ description }}</p>
|
||||
<p v-if="description || $slots.description" :class="[ui.description, size]">
|
||||
<slot v-if="$slots.description" name="description" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>
|
||||
{{ description }}
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<div :class="[label ? ui.container : '']">
|
||||
<slot v-bind="{ error }" />
|
||||
<div :class="[label ? ui.container : '']">
|
||||
<slot v-bind="{ error }" />
|
||||
|
||||
<p v-if="error && typeof error !== 'boolean'" :class="[ui.error, size]">{{ error }}</p>
|
||||
<p v-else-if="help" :class="[ui.help, size]">{{ help }}</p>
|
||||
</div>
|
||||
</label>
|
||||
<p v-if="(typeof error === 'string' && error) || $slots.error" :class="[ui.error, size]">
|
||||
<slot v-if="$slots.error" name="error" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>
|
||||
{{ error }}
|
||||
</template>
|
||||
</p>
|
||||
<p v-else-if="help || $slots.help" :class="[ui.help, size]">
|
||||
<slot v-if="$slots.help" name="help" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>
|
||||
{{ help }}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, provide, inject } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { FormError } from '../../types/form'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { computed, defineComponent, provide, inject, ref, toRef } from 'vue'
|
||||
import type { Ref, PropType } from 'vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { FormError, InjectedFormGroupValue, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { formGroup } from '#ui/ui.config'
|
||||
import { uid } from '../../utils/uid'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof formGroup>(appConfig.ui.strategy, appConfig.ui.formGroup, formGroup)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
@@ -40,10 +58,10 @@ export default defineComponent({
|
||||
default: null
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
type: String as PropType<keyof typeof config.size>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.formGroup.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
label: {
|
||||
@@ -70,18 +88,17 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.formGroup>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props, { attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.formGroup>>(() => defuTwMerge({}, props.ui, appConfig.ui.formGroup))
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
setup (props) {
|
||||
const { ui, attrs } = useUI('formGroup', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
|
||||
|
||||
@@ -91,19 +108,21 @@ export default defineComponent({
|
||||
: formErrors?.value?.find((error) => error.path === props.name)?.message
|
||||
})
|
||||
|
||||
const size = computed(() => ui.value.size[props.size ?? appConfig.ui.input.default.size])
|
||||
const size = computed(() => ui.value.size[props.size ?? config.default.size])
|
||||
const inputId = ref(uid())
|
||||
|
||||
provide('form-group', {
|
||||
provide<InjectedFormGroupValue>('form-group', {
|
||||
error,
|
||||
inputId,
|
||||
name: computed(() => props.name),
|
||||
size: computed(() => props.size)
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
wrapperClass,
|
||||
attrs,
|
||||
inputId,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
size,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div :class="ui.wrapper">
|
||||
<input
|
||||
:id="inputId"
|
||||
ref="input"
|
||||
:name="name"
|
||||
:value="modelValue"
|
||||
@@ -31,19 +32,20 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, onMounted, defineComponent } from 'vue'
|
||||
import { ref, computed, toRef, onMounted, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { NestedKeyOf, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { input } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof input>(appConfig.ui.strategy, appConfig.ui.input, input)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -59,6 +61,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -85,7 +91,7 @@ export default defineComponent({
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.loadingIcon
|
||||
default: () => config.default.loadingIcon
|
||||
},
|
||||
leadingIcon: {
|
||||
type: String,
|
||||
@@ -112,26 +118,26 @@ export default defineComponent({
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.size,
|
||||
type: String as PropType<keyof typeof config.size>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.input.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.color,
|
||||
type: String as PropType<keyof typeof config.color | typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.input.color)].includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.variant,
|
||||
type: String as PropType<keyof typeof config.variant | NestedKeyOf<typeof config.color>>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.input.variant),
|
||||
...Object.values(appConfig.ui.input.color).flatMap(value => Object.keys(value))
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
@@ -139,21 +145,20 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.input>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'blur'],
|
||||
setup (props, { emit, attrs, slots }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
setup (props, { emit, slots }) {
|
||||
const { ui, attrs } = useUI('input', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.input>>(() => defuTwMerge({}, props.ui, appConfig.ui.input))
|
||||
|
||||
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup()
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const { emitFormBlur, emitFormInput, size, color, inputId, name } = useFormGroup(props, config)
|
||||
|
||||
const input = ref<HTMLInputElement | null>(null)
|
||||
|
||||
@@ -179,8 +184,6 @@ export default defineComponent({
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
const inputClass = computed(() => {
|
||||
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
@@ -255,13 +258,15 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
inputId,
|
||||
input,
|
||||
isLeading,
|
||||
isTrailing,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
inputClass,
|
||||
leadingIconName,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div :class="ui.wrapper">
|
||||
<div class="flex items-center h-5">
|
||||
<input
|
||||
:id="`${name}-${value}`"
|
||||
:id="inputId"
|
||||
v-model="pick"
|
||||
:name="name"
|
||||
:required="required"
|
||||
@@ -15,7 +15,7 @@
|
||||
>
|
||||
</div>
|
||||
<div v-if="label || $slots.label" class="ms-3 text-sm">
|
||||
<label :for="`${name}-${value}`" :class="ui.label">
|
||||
<label :for="inputId" :class="ui.label">
|
||||
<slot name="label">{{ label }}</slot>
|
||||
<span v-if="required" :class="ui.required">*</span>
|
||||
</label>
|
||||
@@ -27,22 +27,29 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { radio } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
import { uid } from '../../utils/uid'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof radio>(appConfig.ui.strategy, appConfig.ui.radio, radio)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
// A default value is needed here to bind the label
|
||||
default: () => uid()
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean],
|
||||
default: null
|
||||
@@ -72,8 +79,8 @@ export default defineComponent({
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.radio.default.color,
|
||||
type: String as PropType<typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return appConfig.ui.colors.includes(value)
|
||||
}
|
||||
@@ -82,20 +89,20 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.radio>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup (props, { emit, attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('radio', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.radio>>(() => defuTwMerge({}, props.ui, appConfig.ui.radio))
|
||||
|
||||
const { emitFormChange, formGroup } = useFormGroup()
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const { emitFormChange, color, name, inputId } = useFormGroup(props)
|
||||
|
||||
const pick = computed({
|
||||
get () {
|
||||
@@ -109,8 +116,6 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
const inputClass = computed(() => {
|
||||
return twMerge(twJoin(
|
||||
ui.value.base,
|
||||
@@ -122,11 +127,13 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
inputId,
|
||||
attrs,
|
||||
pick,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
inputClass
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<input
|
||||
:id="inputId"
|
||||
ref="input"
|
||||
v-model.number="value"
|
||||
:name="name"
|
||||
@@ -19,16 +20,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { range } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
const config = mergeConfig<typeof range>(appConfig.ui.strategy, appConfig.ui.range, range)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
@@ -37,6 +41,10 @@ export default defineComponent({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -58,15 +66,15 @@ export default defineComponent({
|
||||
default: 1
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.range.default.size,
|
||||
type: String as PropType<keyof typeof config.size>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.range.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.range.default.color,
|
||||
type: String as PropType<typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return appConfig.ui.colors.includes(value)
|
||||
}
|
||||
@@ -75,21 +83,20 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.range>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup (props, { emit, attrs }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('range', toRef(props, 'ui'), config)
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.range>>(() => defuTwMerge({}, props.ui, appConfig.ui.range))
|
||||
|
||||
const { emitFormChange, formGroup } = useFormGroup()
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const { emitFormChange, inputId, color, size, name } = useFormGroup(props, config)
|
||||
|
||||
const value = computed({
|
||||
get () {
|
||||
@@ -109,7 +116,7 @@ export default defineComponent({
|
||||
return twMerge(twJoin(
|
||||
ui.value.wrapper,
|
||||
ui.value.size[size.value]
|
||||
), attrs.class as string)
|
||||
), props.class)
|
||||
})
|
||||
|
||||
const inputClass = computed(() => {
|
||||
@@ -161,9 +168,12 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
inputId,
|
||||
value,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div :class="ui.wrapper">
|
||||
<select
|
||||
:id="inputId"
|
||||
:name="name"
|
||||
:value="modelValue"
|
||||
:required="required"
|
||||
@@ -53,19 +54,20 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType, ComputedRef } from 'vue'
|
||||
import { get, omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig, get } from '../../utils'
|
||||
import type { NestedKeyOf, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { select } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof select>(appConfig.ui.strategy, appConfig.ui.select, select)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -77,6 +79,10 @@ export default defineComponent({
|
||||
type: [String, Number, Object],
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -99,7 +105,7 @@ export default defineComponent({
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.loadingIcon
|
||||
default: () => config.default.loadingIcon
|
||||
},
|
||||
leadingIcon: {
|
||||
type: String,
|
||||
@@ -107,7 +113,7 @@ export default defineComponent({
|
||||
},
|
||||
trailingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.trailingIcon
|
||||
default: () => config.default.trailingIcon
|
||||
},
|
||||
trailing: {
|
||||
type: Boolean,
|
||||
@@ -130,26 +136,26 @@ export default defineComponent({
|
||||
default: () => []
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.size,
|
||||
type: String as PropType<keyof typeof config.size>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.select.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.color,
|
||||
type: String as PropType<keyof typeof config.color | typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.select.color)].includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.variant,
|
||||
type: String as PropType<keyof typeof config.variant | NestedKeyOf<typeof config.color>>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.select.variant),
|
||||
...Object.values(appConfig.ui.select.color).flatMap(value => Object.keys(value))
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
@@ -165,22 +171,20 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.select>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup (props, { emit, attrs, slots }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defuTwMerge({}, props.ui, appConfig.ui.select))
|
||||
|
||||
const { emitFormChange, formGroup } = useFormGroup()
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
setup (props, { emit, slots }) {
|
||||
const { ui, attrs } = useUI('select', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const { emitFormChange, inputId, color, size, name } = useFormGroup(props, config)
|
||||
|
||||
const onInput = (event: InputEvent) => {
|
||||
emit('update:modelValue', (event.target as HTMLInputElement).value)
|
||||
@@ -243,8 +247,6 @@ export default defineComponent({
|
||||
return foundOption[props.valueAttribute]
|
||||
})
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
const selectClass = computed(() => {
|
||||
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
@@ -318,14 +320,16 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
inputId,
|
||||
normalizedOptionsWithPlaceholder,
|
||||
normalizedValue,
|
||||
isLeading,
|
||||
isTrailing,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
selectClass,
|
||||
leadingIconName,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:multiple="multiple"
|
||||
:disabled="disabled || loading"
|
||||
as="div"
|
||||
:class="wrapperClass"
|
||||
:class="ui.wrapper"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<input
|
||||
@@ -28,7 +28,7 @@
|
||||
class="inline-flex w-full"
|
||||
>
|
||||
<slot :open="open" :disabled="disabled" :loading="loading">
|
||||
<button :class="selectClass" :disabled="disabled || loading" type="button" v-bind="attrs">
|
||||
<button :id="inputId" :class="selectClass" :disabled="disabled || loading" type="button" v-bind="attrs">
|
||||
<span v-if="(isLeading && leadingIconName) || $slots.leading" :class="leadingWrapperIconClass">
|
||||
<slot name="leading" :disabled="disabled" :loading="loading">
|
||||
<UIcon :name="leadingIconName" :class="leadingIconClass" />
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<slot name="label">
|
||||
<span v-if="multiple && Array.isArray(modelValue) && modelValue.length" class="block truncate">{{ modelValue.length }} selected</span>
|
||||
<span v-else-if="!multiple && modelValue" class="block truncate">{{ typeof modelValue === 'string' ? modelValue : modelValue[optionAttribute] }}</span>
|
||||
<span v-else-if="!multiple && modelValue" class="block truncate">{{ ['string', 'number'].includes(typeof modelValue) ? modelValue : modelValue[optionAttribute] }}</span>
|
||||
<span v-else class="block truncate" :class="uiMenu.placeholder">{{ placeholder || ' ' }}</span>
|
||||
</slot>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
/>
|
||||
<span v-else-if="option.chip" :class="uiMenu.option.chip.base" :style="{ background: `#${option.chip}` }" />
|
||||
|
||||
<span class="truncate">{{ typeof option === 'string' ? option : option[optionAttribute] }}</span>
|
||||
<span class="truncate">{{ ['string', 'number'].includes(typeof option) ? option : option[optionAttribute] }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, watch, defineComponent } from 'vue'
|
||||
import { ref, computed, toRef, watch, defineComponent } from 'vue'
|
||||
import type { PropType, ComponentPublicInstance } from 'vue'
|
||||
import {
|
||||
Combobox as HCombobox,
|
||||
@@ -131,20 +131,22 @@ import {
|
||||
} from '@headlessui/vue'
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import type { PopperOptions } from '../../types/popper'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { PopperOptions, NestedKeyOf, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { select, selectMenu } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof select>(appConfig.ui.strategy, appConfig.ui.select, select)
|
||||
|
||||
const configMenu = mergeConfig<typeof selectMenu>(appConfig.ui.strategy, appConfig.ui.selectMenu, selectMenu)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -174,6 +176,10 @@ export default defineComponent({
|
||||
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[] | string[]>,
|
||||
default: () => []
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -188,7 +194,7 @@ export default defineComponent({
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.loadingIcon
|
||||
default: () => config.default.loadingIcon
|
||||
},
|
||||
leadingIcon: {
|
||||
type: String,
|
||||
@@ -196,7 +202,7 @@ export default defineComponent({
|
||||
},
|
||||
trailingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.trailingIcon
|
||||
default: () => config.default.trailingIcon
|
||||
},
|
||||
trailing: {
|
||||
type: Boolean,
|
||||
@@ -212,7 +218,7 @@ export default defineComponent({
|
||||
},
|
||||
selectedIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.selectMenu.default.selectedIcon
|
||||
default: () => configMenu.default.selectedIcon
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
@@ -247,26 +253,26 @@ export default defineComponent({
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.size,
|
||||
type: String as PropType<keyof typeof config.size>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.select.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.color,
|
||||
type: String as PropType<keyof typeof config.color | typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.select.color)].includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.variant,
|
||||
type: String as PropType<keyof typeof config.variant | NestedKeyOf<typeof config.color>>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.select.variant),
|
||||
...Object.values(appConfig.ui.select.color).flatMap(value => Object.keys(value))
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
@@ -290,35 +296,33 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.select>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
},
|
||||
uiMenu: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.selectMenu>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof configMenu & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'open', 'close', 'change'],
|
||||
setup (props, { emit, attrs, slots }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
setup (props, { emit, slots }) {
|
||||
const { ui, attrs } = useUI('select', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defuTwMerge({}, props.ui, appConfig.ui.select))
|
||||
const uiMenu = computed<Partial<typeof appConfig.ui.selectMenu>>(() => defuTwMerge({}, props.uiMenu, appConfig.ui.selectMenu))
|
||||
const { ui: uiMenu } = useUI('selectMenu', toRef(props, 'uiMenu'), configMenu)
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
|
||||
|
||||
const [trigger, container] = usePopper(popper.value)
|
||||
const { emitFormBlur, emitFormChange, formGroup } = useFormGroup()
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const { emitFormBlur, emitFormChange, inputId, color, size, name } = useFormGroup(props, config)
|
||||
|
||||
const query = ref('')
|
||||
const searchInput = ref<ComponentPublicInstance<HTMLElement>>()
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
const selectClass = computed(() => {
|
||||
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
@@ -407,7 +411,7 @@ export default defineComponent({
|
||||
|
||||
return (props.options as any[]).filter((option: any) => {
|
||||
return (props.searchAttributes?.length ? props.searchAttributes : [props.optionAttribute]).some((searchAttribute: any) => {
|
||||
return typeof option === 'string' ? option.search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
|
||||
return ['string', 'number'].includes(typeof option) ? option.toString().search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -437,14 +441,18 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
uiMenu,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
inputId,
|
||||
trigger,
|
||||
container,
|
||||
isLeading,
|
||||
isTrailing,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
selectClass,
|
||||
leadingIconName,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div :class="ui.wrapper">
|
||||
<textarea
|
||||
:id="inputId"
|
||||
ref="textarea"
|
||||
:value="modelValue"
|
||||
:name="name"
|
||||
@@ -18,18 +19,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, watch, onMounted, nextTick, defineComponent } from 'vue'
|
||||
import { ref, computed, toRef, watch, onMounted, nextTick, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from 'lodash-es'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { NestedKeyOf, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { textarea } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
const config = mergeConfig<typeof textarea>(appConfig.ui.strategy, appConfig.ui.textarea, textarea)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
@@ -38,6 +40,10 @@ export default defineComponent({
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -75,26 +81,26 @@ export default defineComponent({
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.textarea.default.size,
|
||||
type: String as PropType<keyof typeof config.size>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.textarea.size).includes(value)
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.textarea.default.color,
|
||||
type: String as PropType<keyof typeof config.color | typeof colors[number]>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.textarea.color)].includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.textarea.default.variant,
|
||||
type: String as PropType<keyof typeof config.variant | NestedKeyOf<typeof config.color>>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.textarea.variant),
|
||||
...Object.values(appConfig.ui.textarea.color).flatMap(value => Object.keys(value))
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
@@ -102,24 +108,23 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: undefined
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.textarea>>,
|
||||
default: () => ({})
|
||||
type: Object as PropType<Partial<typeof config & { strategy?: Strategy }>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'blur'],
|
||||
setup (props, { emit, attrs }) {
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('textarea', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const { emitFormBlur, emitFormInput, inputId, color, size, name } = useFormGroup(props, config)
|
||||
|
||||
const textarea = ref<HTMLTextAreaElement | null>(null)
|
||||
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.textarea>>(() => defuTwMerge({}, props.ui, appConfig.ui.textarea))
|
||||
|
||||
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup()
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
|
||||
const autoFocus = () => {
|
||||
if (props.autofocus) {
|
||||
textarea.value?.focus()
|
||||
@@ -177,8 +182,6 @@ export default defineComponent({
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
|
||||
|
||||
const textareaClass = computed(() => {
|
||||
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
@@ -194,11 +197,13 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: omit(attrs, ['class']),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
inputId,
|
||||
textarea,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
textareaClass,
|
||||
onInput,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user