mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 20:48:12 +01:00
Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
189bd4cd3e | ||
|
|
871d3b3a85 | ||
|
|
248b0a68c6 | ||
|
|
396aae7563 | ||
|
|
dc1979cae1 | ||
|
|
d51ad93f40 | ||
|
|
c59595f2c6 | ||
|
|
a3aba1abad | ||
|
|
c37a927b4e | ||
|
|
05ea5d2d78 | ||
|
|
963d81324c | ||
|
|
927b63fa2e | ||
|
|
cefe5a76e0 | ||
|
|
a9300db91e | ||
|
|
8e1aa2f1b6 | ||
|
|
5221294f78 | ||
|
|
1bc055935e | ||
|
|
e25be118b7 | ||
|
|
93aebe6fc6 | ||
|
|
2b3dc8d065 | ||
|
|
5cf3bcf32d | ||
|
|
3b183ac9cd | ||
|
|
3400e17d17 | ||
|
|
4cd38ecc5a | ||
|
|
8380607a85 | ||
|
|
4561816b50 | ||
|
|
e6d1106b83 | ||
|
|
94f1c4e6a0 | ||
|
|
09d0ea27ab | ||
|
|
66ab95a2be | ||
|
|
2cd620899f | ||
|
|
0300be8539 | ||
|
|
5bd5dc2bca | ||
|
|
572b7a5984 | ||
|
|
ab2abae48a | ||
|
|
8298b62f21 | ||
|
|
10890e6704 | ||
|
|
3af39cacf7 | ||
|
|
58e3958390 | ||
|
|
3dd0492f91 | ||
|
|
9a73c5fb64 | ||
|
|
e7cfca2aa7 | ||
|
|
dc77cf292b | ||
|
|
05503e564c | ||
|
|
0420a17c1d | ||
|
|
a9578f8c50 | ||
|
|
d9ae1ee5b0 | ||
|
|
9e5f265f42 | ||
|
|
041f9e17de | ||
|
|
df1e4a40ca | ||
|
|
f005cbb95e | ||
|
|
d2a8a07a21 | ||
|
|
b0440f81ce | ||
|
|
4f4a659ccc | ||
|
|
beffde1849 | ||
|
|
959c968420 | ||
|
|
7cccbcfef8 | ||
|
|
df455db3ca | ||
|
|
9fc786eda0 | ||
|
|
92a9ac0c85 | ||
|
|
5a9910c2a3 | ||
|
|
a0f485c49d | ||
|
|
e92f341224 | ||
|
|
c4e0e5a685 | ||
|
|
72ee359b73 | ||
|
|
8a2b2604be | ||
|
|
d94c1b5b15 | ||
|
|
b0486140e2 | ||
|
|
b7d9c08a1c | ||
|
|
dbcb02d0ea | ||
|
|
208acca1e9 | ||
|
|
82e152be02 | ||
|
|
403899f11a | ||
|
|
914d156103 | ||
|
|
0f06b7c3fe | ||
|
|
2c454b528a | ||
|
|
b28ae68945 | ||
|
|
1171724791 | ||
|
|
ad63c72d37 | ||
|
|
d7f74d1868 | ||
|
|
a0ffdce36c | ||
|
|
a31e7dfa28 | ||
|
|
c91ea60c84 | ||
|
|
a11248fd33 | ||
|
|
2fc579560e | ||
|
|
5f8fe8559f | ||
|
|
46b444a3e0 | ||
|
|
0ea1f310a9 | ||
|
|
b1825ffa7d | ||
|
|
7be48fd6f3 | ||
|
|
d292706967 | ||
|
|
31d571abb5 | ||
|
|
2ec28e7cbd | ||
|
|
908235e8dd | ||
|
|
5a4d0e1097 | ||
|
|
773a23f969 | ||
|
|
7554a10206 | ||
|
|
914cb03a5d | ||
|
|
4afdd3bd64 | ||
|
|
05b8a22eec | ||
|
|
7e08e5b024 | ||
|
|
d15e8163e7 | ||
|
|
2cc5c0d810 | ||
|
|
e08263ff38 | ||
|
|
57c3023909 | ||
|
|
b874bb5061 | ||
|
|
cbe2b1bfb8 | ||
|
|
3b432fde7a | ||
|
|
a79c165eee | ||
|
|
cd2b671075 | ||
|
|
3de6b349d8 | ||
|
|
eaf0043da6 | ||
|
|
b78fcf91a4 | ||
|
|
e0f1798f07 | ||
|
|
e4233718a6 | ||
|
|
a11abd6347 | ||
|
|
db346652b8 | ||
|
|
52b614fcb0 | ||
|
|
5dffa868b1 | ||
|
|
cbd8cc49fb | ||
|
|
f3c6f83232 | ||
|
|
80a9738490 | ||
|
|
54b6f734a3 | ||
|
|
41a5238579 | ||
|
|
c92dc980c9 | ||
|
|
2451541d7d | ||
|
|
103a20897c | ||
|
|
e50f377b94 | ||
|
|
0bfe4b01bd | ||
|
|
80e09df342 | ||
|
|
7a2845d75e |
@@ -2,14 +2,42 @@ module.exports = {
|
||||
root: true,
|
||||
extends: ['@nuxt/eslint-config'],
|
||||
rules: {
|
||||
'semi': ['error', 'never'],
|
||||
// General
|
||||
semi: ['error', 'never'],
|
||||
quotes: ['error', 'single'],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
'comma-spacing': ['error', { before: false, after: true }],
|
||||
'keyword-spacing': ['error', { before: true, after: true }],
|
||||
'space-before-function-paren': ['error', 'always'],
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
'arrow-spacing': ['error', { before: true, after: true }],
|
||||
'key-spacing': ['error', { beforeColon: false, afterColon: true, mode: 'strict' }],
|
||||
'space-before-blocks': ['error', 'always'],
|
||||
'space-infix-ops': ['error', { int32Hint: false }],
|
||||
|
||||
// Typescript
|
||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||
|
||||
// Vuejs
|
||||
'vue/multi-word-component-names': 0,
|
||||
'vue/max-attributes-per-line': ['error', {
|
||||
singleline: {
|
||||
max: 5
|
||||
'vue/html-indent': ['error', 2],
|
||||
'vue/script-indent': ['error', 2, { baseIndent: 0 }],
|
||||
'vue/keyword-spacing': ['error', { before: true, after: true }],
|
||||
'vue/object-curly-spacing': ['error', 'always'],
|
||||
'vue/key-spacing': ['error', { beforeColon: false, afterColon: true, mode: 'strict' }],
|
||||
'vue/arrow-spacing': ['error', { before: true, after: true }],
|
||||
'vue/array-bracket-spacing': ['error', 'never'],
|
||||
'vue/block-spacing': ['error', 'always'],
|
||||
'vue/brace-style': ['error', 'stroustrup', { allowSingleLine: true }],
|
||||
'vue/space-infix-ops': ['error', { int32Hint: false }],
|
||||
'vue/max-attributes-per-line': [
|
||||
'error',
|
||||
{
|
||||
singleline: {
|
||||
max: 5
|
||||
}
|
||||
}
|
||||
}]
|
||||
],
|
||||
'vue/padding-line-between-blocks': ['error', 'always']
|
||||
}
|
||||
}
|
||||
|
||||
6
.github/workflows/ci-dev.yml
vendored
6
.github/workflows/ci-dev.yml
vendored
@@ -52,12 +52,12 @@ jobs:
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Release Edge
|
||||
if: github.event_name == 'push'
|
||||
run: ./scripts/release-edge.sh
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -52,12 +52,12 @@ jobs:
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Version Check
|
||||
id: check
|
||||
uses: EndBug/version-check@v2
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ dist
|
||||
.history
|
||||
.vercel
|
||||
.idea
|
||||
.env
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
74
CHANGELOG.md
74
CHANGELOG.md
@@ -1,5 +1,79 @@
|
||||
# Changelog
|
||||
|
||||
## [2.7.0](https://github.com/nuxtlabs/ui/compare/v2.6.0...v2.7.0) (2023-08-01)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **Link:** rename from `LinkCustom` and add `exact-query` / `exact-hash` props
|
||||
* **Badge:** add colors and variants (solid has changed)
|
||||
* **SelectMenu:** invert `ui` and `ui-select` props (#432)
|
||||
|
||||
### Features
|
||||
|
||||
* **Alert:** new component ([#449](https://github.com/nuxtlabs/ui/issues/449)) ([ab2abae](https://github.com/nuxtlabs/ui/commit/ab2abae48a03200d273b4f0cb3ce300fffcee64b))
|
||||
* **Badge:** add colors and variants (solid has changed) ([05503e5](https://github.com/nuxtlabs/ui/commit/05503e564c3e8dfaee2e27e25b3409edb4c555a1))
|
||||
* **Badge:** rename `outline` to `subtle` + add `soft` variants ([5bd5dc2](https://github.com/nuxtlabs/ui/commit/5bd5dc2bcaeb59d4b0a1aea802bd3e2b2160ad53))
|
||||
* **CommandPalette:** bind active and selected to scoped slot ([#441](https://github.com/nuxtlabs/ui/issues/441)) ([b0440f8](https://github.com/nuxtlabs/ui/commit/b0440f81ce2960704ed7386ec069e52ecd7bb787))
|
||||
* **FormGroup:** add `size` prop and theme options ([#391](https://github.com/nuxtlabs/ui/issues/391)) ([d2a8a07](https://github.com/nuxtlabs/ui/commit/d2a8a07a21a4943144bd990fdbfe645ea308ae7b))
|
||||
* **Form:** new component ([#439](https://github.com/nuxtlabs/ui/issues/439)) ([a3aba1a](https://github.com/nuxtlabs/ui/commit/a3aba1abadd569b69f15697bcc5908b49e0a7f8a))
|
||||
* **Link:** rename from `LinkCustom` and add `exact-query` / `exact-hash` props ([cefe5a7](https://github.com/nuxtlabs/ui/commit/cefe5a76e0a4820f648d23734228540e3010b233))
|
||||
* **Notification:** support html with `title` and `description` slots ([#431](https://github.com/nuxtlabs/ui/issues/431)) ([df455db](https://github.com/nuxtlabs/ui/commit/df455db3caeb689ac1f24f224606183d4f5135ea))
|
||||
* **Range:** increase narrowed surface ([#459](https://github.com/nuxtlabs/ui/issues/459)) ([3b183ac](https://github.com/nuxtlabs/ui/commit/3b183ac9cde86cf3ab6fbdc5dd124d66deec865d))
|
||||
* **SelectMenu:** add `value-attribute` prop ([#429](https://github.com/nuxtlabs/ui/issues/429)) ([959c968](https://github.com/nuxtlabs/ui/commit/959c968420945fc0a143edb909c1289123fd62cb))
|
||||
* **Tabs:** new component ([#450](https://github.com/nuxtlabs/ui/issues/450)) ([8298b62](https://github.com/nuxtlabs/ui/commit/8298b62f216712fc156ef1a114d6cff3a573efdb))
|
||||
* **ui:** apply primary bg on `::selection` ([09d0ea2](https://github.com/nuxtlabs/ui/commit/09d0ea27ab36b0655106f0cf000f2c13c63b92bd))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **FormGroup:** `required` star display ([3dd0492](https://github.com/nuxtlabs/ui/commit/3dd0492f91422c8248691ac9d3f5de6d37278721))
|
||||
* **FormGroup:** err when no prop defined ([93aebe6](https://github.com/nuxtlabs/ui/commit/93aebe6fc614bc2a78109f632297c3843f7a785a))
|
||||
* **FormGroup:** missing imports ([#470](https://github.com/nuxtlabs/ui/issues/470)) ([dc1979c](https://github.com/nuxtlabs/ui/commit/dc1979cae1478cf864aab5ea573cc87d070185ce))
|
||||
* **FormGroup:** set `size` default to null ([c59595f](https://github.com/nuxtlabs/ui/commit/c59595f2c6cf414bf04bb5995ba029a59ef8035b))
|
||||
* **Form:** return state on validate ([#472](https://github.com/nuxtlabs/ui/issues/472)) ([248b0a6](https://github.com/nuxtlabs/ui/commit/248b0a68c675255a586d0ff61b0561f2f47b2682))
|
||||
* **LinkCustom:** `exact` prop wasn't working ([82e152b](https://github.com/nuxtlabs/ui/commit/82e152be02ca7b8fc5d6e27ffd81ff3f0d8a8f80)), closes [#417](https://github.com/nuxtlabs/ui/issues/417)
|
||||
* **LinkCustom:** improve prop binding and prevent error with externals ([914d156](https://github.com/nuxtlabs/ui/commit/914d156103d5ebca6b14ea705ed329508bf66d74))
|
||||
* **Link:** handle `disabled` prop ([396aae7](https://github.com/nuxtlabs/ui/commit/396aae75638da88a736179f71324374d74a89d70)), closes [#473](https://github.com/nuxtlabs/ui/issues/473)
|
||||
* **module:** ensure `red` color is safelisted for form elements ([208acca](https://github.com/nuxtlabs/ui/commit/208acca1e9269494310ff000104b21e999b66cf8)), closes [#423](https://github.com/nuxtlabs/ui/issues/423) [#373](https://github.com/nuxtlabs/ui/issues/373)
|
||||
* **module:** omit colors defined as strings ([927b63f](https://github.com/nuxtlabs/ui/commit/927b63fa2e33cc5ee303554c0c43c9e89156b7c8))
|
||||
* **module:** safelist all colors for `toast.add` ([2cd6208](https://github.com/nuxtlabs/ui/commit/2cd620899f3e997357f6274cc7a0bfc79a8277b6)), closes [#375](https://github.com/nuxtlabs/ui/issues/375) [#440](https://github.com/nuxtlabs/ui/issues/440)
|
||||
* **module:** smart safelisting for components in snake case ([e25be11](https://github.com/nuxtlabs/ui/commit/e25be118b7fe4bfd4ec26be9aacfb0d87ee94d81)), closes [#461](https://github.com/nuxtlabs/ui/issues/461)
|
||||
* **Popover:** hover mode ([#453](https://github.com/nuxtlabs/ui/issues/453)) ([10890e6](https://github.com/nuxtlabs/ui/commit/10890e6704e9884a7e86b37d0dc72e8f9c5177e7))
|
||||
* **SelectMenu:** invert `ui` and `ui-select` props ([#432](https://github.com/nuxtlabs/ui/issues/432)) ([7cccbcf](https://github.com/nuxtlabs/ui/commit/7cccbcfef899a64d63c8d33639a2d0da155c46cb))
|
||||
* **Table:** hide data when loading state is active ([#460](https://github.com/nuxtlabs/ui/issues/460)) ([2b3dc8d](https://github.com/nuxtlabs/ui/commit/2b3dc8d065c35671434975a07af4b2182a793b58))
|
||||
|
||||
## [2.6.0](https://github.com/nuxtlabs/ui/compare/v2.5.0...v2.6.0) (2023-07-18)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **Avatar:** bind component attributes to img element (#421)
|
||||
|
||||
### Features
|
||||
|
||||
* **Accordion:** add `multiple` prop and close others by default ([#364](https://github.com/nuxtlabs/ui/issues/364)) ([b78fcf9](https://github.com/nuxtlabs/ui/commit/b78fcf91a4b592a6ca83ca4333e1d6658ec6458d))
|
||||
* **Accordion:** new component ([#301](https://github.com/nuxtlabs/ui/issues/301)) ([e50f377](https://github.com/nuxtlabs/ui/commit/e50f377b946996efd4546195e528fbed59dcb22f))
|
||||
* **Avatar:** bind component attributes to img element ([#421](https://github.com/nuxtlabs/ui/issues/421)) ([773a23f](https://github.com/nuxtlabs/ui/commit/773a23f969d2dbbbcb01582f9e127e02f0248be9))
|
||||
* **Modal:** add `prevent-close` prop ([2cc5c0d](https://github.com/nuxtlabs/ui/commit/2cc5c0d810e30b889081d1f457d725004bd0b933)), closes [#303](https://github.com/nuxtlabs/ui/issues/303)
|
||||
* **SelectMenu:** handle async search ([#426](https://github.com/nuxtlabs/ui/issues/426)) ([5f8fe85](https://github.com/nuxtlabs/ui/commit/5f8fe8559f2eb12d3916387d5acf65a391bfa0eb))
|
||||
* **Slideover:** add `prevent-close` prop ([d15e816](https://github.com/nuxtlabs/ui/commit/d15e8163e7d7eb3eb7624bb982c139581902d596))
|
||||
* **Table:** add click event for the entire row ([#353](https://github.com/nuxtlabs/ui/issues/353)) ([d292706](https://github.com/nuxtlabs/ui/commit/d2927069673840dad58d388ab982b5488642edec))
|
||||
* **Table:** allow columns `class` customization ([5dffa86](https://github.com/nuxtlabs/ui/commit/5dffa868b11760610ea0bf9f2ce37931cdac4eb9)), closes [#366](https://github.com/nuxtlabs/ui/issues/366)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Accordion:** missing `ref` import from vue ([3de6b34](https://github.com/nuxtlabs/ui/commit/3de6b349d8b043ed2524bd6418f350ebb4557adb))
|
||||
* **Accordion:** solve the shift between buttons when they are opened ([#379](https://github.com/nuxtlabs/ui/issues/379)) ([eaf0043](https://github.com/nuxtlabs/ui/commit/eaf0043da660dfb168a7d4f2312d4344598c2f86))
|
||||
* **ButtonGroup:** err when no props on buttons ([80a9738](https://github.com/nuxtlabs/ui/commit/80a97384909891a14edca4ff760d5c81b26b3307)), closes [#360](https://github.com/nuxtlabs/ui/issues/360)
|
||||
* **Button:** missing `disabled` state on some variants ([41a5238](https://github.com/nuxtlabs/ui/commit/41a523857902b1674ba7f6021938f68d66a2ddbd))
|
||||
* **Modal:** disabling `transition` prop had no effect ([db34665](https://github.com/nuxtlabs/ui/commit/db346652b829ea02b8b1f5355f7080f5e530dcb2))
|
||||
* **Range:** `disabled` thumb opacity ([c92dc98](https://github.com/nuxtlabs/ui/commit/c92dc980c984cff8e9f9c38eb9524d151523c16b))
|
||||
* **Range:** progress style ([#385](https://github.com/nuxtlabs/ui/issues/385)) ([a79c165](https://github.com/nuxtlabs/ui/commit/a79c165eeeb3e8ea76cd3abc1b8f1218d02b446b))
|
||||
* **SelectMenu:** missing `appear` on transition ([cbe2b1b](https://github.com/nuxtlabs/ui/commit/cbe2b1bfb802f8cb10dd4a0d36a8cefb215debb2)), closes [#400](https://github.com/nuxtlabs/ui/issues/400)
|
||||
* **Table:** fixed row deletion bug on deselect ([#425](https://github.com/nuxtlabs/ui/issues/425)) ([46b444a](https://github.com/nuxtlabs/ui/commit/46b444a3e0cc988c89bfde7442b42b1e82095fc9))
|
||||
|
||||
## [2.5.0](https://github.com/nuxtlabs/ui/compare/v2.4.1...v2.5.0) (2023-06-27)
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ If you want latest updates, please use `@nuxthq/ui-edge` in your `package.json`:
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit http://ui.nuxtlabs.com to view the documentation.
|
||||
Visit https://ui.nuxtlabs.com to view the documentation.
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
1
docs/.env.example
Normal file
1
docs/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
NUXT_UI_KIT_TOKEN=
|
||||
85
docs/app.vue
85
docs/app.vue
@@ -1,29 +1,88 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div>
|
||||
<Header />
|
||||
<UHeader>
|
||||
<template #left>
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
<UContainer class="grid lg:grid-cols-10 lg:gap-8">
|
||||
<DocsAside class="lg:col-span-2" />
|
||||
<span class="hidden sm:block">NuxtLabs</span><span class="sm:text-primary-500 dark:sm:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<div class="lg:col-span-8 min-h-0 flex flex-col">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</UContainer>
|
||||
<template #center>
|
||||
<UDocsSearchButton class="ml-1.5 flg:w-64 xl:w-96" />
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<ColorPicker />
|
||||
|
||||
<UColorModeButton />
|
||||
|
||||
<USocialButton to="https://twitter.com/nuxtlabs" target="_blank" icon="i-simple-icons-twitter" class="hidden lg:inline-flex" />
|
||||
<USocialButton to="https://github.com/nuxtlabs/ui" target="_blank" icon="i-simple-icons-github" class="hidden lg:inline-flex" />
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UAsideAnchors :links="anchors" />
|
||||
<UAsideLinks :links="links" />
|
||||
</template>
|
||||
</UHeader>
|
||||
|
||||
<UMain>
|
||||
<UContainer>
|
||||
<UPage>
|
||||
<template #left>
|
||||
<UAside :links="links" :anchors="anchors" />
|
||||
</template>
|
||||
|
||||
<NuxtPage />
|
||||
</UPage>
|
||||
</UContainer>
|
||||
</UMain>
|
||||
|
||||
<ClientOnly>
|
||||
<DocsSearch />
|
||||
<UDocsSearch :files="files" :links="links" />
|
||||
</ClientOnly>
|
||||
|
||||
<UNotifications />
|
||||
<UNotifications>
|
||||
<template #title="{ title }">
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
|
||||
<template #description="{ description }">
|
||||
<span v-html="description" />
|
||||
</template>
|
||||
</UNotifications>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation())
|
||||
const { data: links } = await useAsyncData('navigation', () => fetchContentNavigation(), {
|
||||
transform: (navigation) => mapContentLinks(navigation)
|
||||
})
|
||||
|
||||
provide('navigation', navigation)
|
||||
const { data: files } = await useLazyAsyncData('files', () => queryContent().where({ _type: 'markdown', navigation: { $ne: false } }).find(), { default: () => [] })
|
||||
|
||||
provide('links', links)
|
||||
|
||||
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/nuxtlabs-ui?file=app.config.ts,app.vue',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-heroicons-rocket-launch-solid',
|
||||
to: 'https://github.com/nuxtlabs/ui/releases',
|
||||
target: '_blank'
|
||||
}]
|
||||
|
||||
// Computed
|
||||
|
||||
@@ -38,14 +97,10 @@ useHead({
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' },
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
||||
],
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
},
|
||||
bodyAttrs: {
|
||||
class: 'antialiased font-sans text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { RouterConfig } from '@nuxt/schema'
|
||||
|
||||
function findHashPosition (hash): { el: any, behavior: ScrollBehavior, top: number } {
|
||||
const el = document.querySelector(hash)
|
||||
// vue-router does not incorporate scroll-margin-top on its own.
|
||||
if (el) {
|
||||
const top = parseFloat(getComputedStyle(el).scrollMarginTop)
|
||||
|
||||
return {
|
||||
el: hash,
|
||||
behavior: 'smooth',
|
||||
top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://router.vuejs.org/api/#routeroptions
|
||||
export default <RouterConfig>{
|
||||
scrollBehavior (to, from, savedPosition) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
// If history back
|
||||
if (savedPosition) {
|
||||
// Handle Suspense resolution
|
||||
return new Promise((resolve) => {
|
||||
nuxtApp.hooks.hookOnce('page:finish', () => {
|
||||
setTimeout(() => resolve(savedPosition), 50)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Scroll to heading on click
|
||||
if (to.hash) {
|
||||
return new Promise((resolve) => {
|
||||
if (to.path === from.path) {
|
||||
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
|
||||
} else {
|
||||
nuxtApp.hooks.hookOnce('page:finish', () => {
|
||||
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Scroll to top of window
|
||||
return { top: 0 }
|
||||
}
|
||||
}
|
||||
23
docs/components/Footer.vue
Normal file
23
docs/components/Footer.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<footer class="flex items-center gap-1.5 mt-12">
|
||||
<div class="flex-1 flex items-baseline gap-1.5 text-sm text-gray-600 dark:text-gray-300 leading-6">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs">
|
||||
<LogoLabs class="text-gray-900 dark:text-white w-14 h-auto" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<NuxtLink :to="`https://github.com/nuxtlabs/ui/releases/tag/v${config.version}`" target="_blank" class="inline-flex">
|
||||
<UBadge :label="`v${config.version}`" />
|
||||
</NuxtLink>
|
||||
|
||||
<div class="flex-1 flex items-center justify-end gap-1.5 -my-1 lg:hidden">
|
||||
<USocialButton to="https://twitter.com/nuxtlabs" target="_blank" icon="i-simple-icons-twitter" />
|
||||
<USocialButton to="https://github.com/nuxtlabs/ui" target="_blank" icon="i-simple-icons-github" />
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const config = useRuntimeConfig().public
|
||||
</script>
|
||||
@@ -1,32 +0,0 @@
|
||||
<template>
|
||||
<header class="sticky top-0 z-50 w-full backdrop-blur flex-none border-b border-gray-200 dark:border-gray-800 bg-white/75 dark:bg-gray-900/75">
|
||||
<UContainer>
|
||||
<HeaderLinks v-model="isDialogOpen" :links="links" />
|
||||
</UContainer>
|
||||
|
||||
<TransitionRoot :show="isDialogOpen" as="template">
|
||||
<Dialog as="div" @close="isDialogOpen = false">
|
||||
<DialogPanel class="fixed inset-0 z-50 overflow-y-auto bg-white dark:bg-gray-900 lg:hidden">
|
||||
<div class="px-4 sm:px-6 sticky top-0 border-b border-gray-200 dark:border-gray-800 bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">
|
||||
<HeaderLinks v-model="isDialogOpen" :links="links" />
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 py-4 sm:py-6">
|
||||
<DocsAsideLinks @click="isDialogOpen = false" />
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Dialog, DialogPanel, TransitionRoot } from '@headlessui/vue'
|
||||
|
||||
const isDialogOpen = ref(false)
|
||||
|
||||
const links = [
|
||||
{ label: 'Documentation', to: '/getting-started' },
|
||||
{ label: 'Components', to: '/elements/avatar' },
|
||||
{ label: 'Examples', to: '/examples' }
|
||||
]
|
||||
</script>
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between gap-3 h-16">
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
<span class="hidden sm:block">NuxtLabs</span><span class="sm:text-primary-500 dark:sm:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end flex-1 -mr-1.5 gap-3">
|
||||
<DocsSearchButton class="ml-1.5 flex-1 lg:flex-none lg:w-48" />
|
||||
|
||||
<div class="flex items-center lg:gap-1.5">
|
||||
<UPopover>
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<ColorPicker />
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<ColorModeButton />
|
||||
|
||||
<UButton
|
||||
to="https://twitter.com/nuxtlabs"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-twitter"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
to="https://github.com/nuxtlabs/ui"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-github"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
:icon="isDialogOpen ? 'i-heroicons-x-mark-20-solid' : 'i-heroicons-bars-3-20-solid'"
|
||||
@click="isDialogOpen = !isDialogOpen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ modelValue: boolean, links: { to: string, label: string }[] }>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const isDialogOpen = useVModel(props, 'modelValue', emit)
|
||||
</script>
|
||||
@@ -1,15 +1,25 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerButton v-for="color in primaryColors" :key="color.value" :color="color" :selected="primary" @select="primary = color" />
|
||||
</div>
|
||||
<UPopover>
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-2">
|
||||
<template #panel>
|
||||
<div class="p-2">
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerPill v-for="color in primaryColors" :key="color.value" :color="color" :selected="primary" @select="primary = color" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerButton v-for="color in grayColors" :key="color.value" :color="color" :selected="gray" @select="gray = color" />
|
||||
</div>
|
||||
</div>
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-2">
|
||||
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerPill v-for="color in grayColors" :key="color.value" :color="color" :selected="gray" @select="gray = color" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<component
|
||||
:is="to ? NuxtLink : 'div'"
|
||||
:to="to"
|
||||
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm leading-6 my-5 last:mb-0 font-normal group relative prose-code:bg-white dark:prose-code:bg-gray-900"
|
||||
:class="[to ? 'hover:!border-primary-500 dark:hover:!border-primary-400 hover:text-primary-500 dark:hover:text-primary-400 border-dashed hover:text-gray-800 dark:hover:text-gray-200' : '']"
|
||||
>
|
||||
<UIcon v-if="!!to" name="i-heroicons-link-20-solid" class="w-3 h-3 absolute right-2 top-2 text-gray-400 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
|
||||
<UIcon v-if="icon" :name="icon" class="w-4 h-4 mr-2 inline-flex items-center align-text-top" :class="color" />
|
||||
|
||||
<ContentSlot :use="$slots.default" unwrap="p" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const NuxtLink = resolveComponent('NuxtLink')
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'text-primary-500 dark:text-primary-400'
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<UKbd class="!my-0 align-text-top">
|
||||
{{ shortcut }}
|
||||
</UKbd>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const { metaSymbol } = useShortcuts()
|
||||
|
||||
const shortcut = computed(() => props.value === 'meta' ? metaSymbol.value : props.value)
|
||||
</script>
|
||||
@@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<div :selected-index="selectedIndex" @change="changeTab">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 border-b-0 rounded-t-md overflow-hidden -mb-px">
|
||||
<div
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
as="template"
|
||||
@click="selectedIndex = index"
|
||||
>
|
||||
<button
|
||||
class="px-4 py-2 focus:outline-none text-sm border-r border-r-gray-200 dark:border-r-gray-700 transition-colors"
|
||||
tabindex="-1"
|
||||
:class="[selectedIndex === index ? 'font-medium text-primary-500 dark:text-primary-400 bg-gray-50 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800']"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="[&>div>pre]:!rounded-t-none">
|
||||
<component :is="selectedTab.component" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const slots = useSlots()
|
||||
|
||||
const selectedIndex = ref(0)
|
||||
|
||||
// Computed
|
||||
|
||||
const tabs = computed(() => slots.default?.().map((slot, index) => {
|
||||
return {
|
||||
label: slot.props?.filename || slot.props?.label || `${index}`,
|
||||
component: slot
|
||||
}
|
||||
}) || [])
|
||||
|
||||
const selectedTab = computed(() => tabs.value.find((_, index) => index === selectedIndex.value))
|
||||
|
||||
// Methods
|
||||
|
||||
function changeTab (index) {
|
||||
selectedIndex.value = index
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false
|
||||
}
|
||||
</script>
|
||||
@@ -2,9 +2,9 @@
|
||||
<div>
|
||||
<div v-if="propsToSelect.length" class="relative flex border border-gray-200 dark:border-gray-700 rounded-t-md overflow-hidden not-prose">
|
||||
<div v-for="prop in propsToSelect" :key="prop.name" class="flex flex-col gap-0.5 justify-between py-1.5 font-medium bg-gray-50 dark:bg-gray-800 border-r border-r-gray-200 dark:border-r-gray-700">
|
||||
<label :for="`prop-${prop.name}`" class="block text-xs px-3 font-medium text-gray-400 dark:text-gray-500 -my-px">{{ prop.label }}</label>
|
||||
<label :for="`prop-${prop.name}`" class="block text-xs px-2.5 font-medium text-gray-400 dark:text-gray-500 -my-px">{{ prop.label }}</label>
|
||||
<UCheckbox
|
||||
v-if="prop.type === 'boolean'"
|
||||
v-if="prop.type.startsWith('boolean')"
|
||||
v-model="componentProps[prop.name]"
|
||||
:name="`prop-${prop.name}`"
|
||||
tabindex="-1"
|
||||
@@ -16,7 +16,7 @@
|
||||
:options="prop.options"
|
||||
:name="`prop-${prop.name}`"
|
||||
variant="none"
|
||||
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md', wrapper: 'relative inline-flex' }"
|
||||
:ui-menu="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md', wrapper: 'relative inline-flex' }"
|
||||
class="!py-0"
|
||||
tabindex="-1"
|
||||
:popper="{ strategy: 'fixed', placement: 'bottom-start' }"
|
||||
@@ -211,8 +211,9 @@ function renderObject (obj: any) {
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify(props)}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
dark: 'material-palenight'
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}), { watch: [code] })
|
||||
|
||||
@@ -23,14 +23,15 @@ const name = `U${useUpperFirst(camelName)}`
|
||||
const preset = appConfig.ui[camelName]
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-preset`, () => transformContent('content:_markdown.md', `
|
||||
\`\`\`json
|
||||
\`\`\`json [appConfig.ui.${camelName}]
|
||||
${JSON.stringify(preset, null, 2)}
|
||||
\`\`\`\
|
||||
`, {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
dark: 'material-palenight'
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -1,36 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<table class="table-fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-[25%]">
|
||||
Prop
|
||||
</th>
|
||||
<th class="w-[50%]">
|
||||
Default
|
||||
</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="prop in metaProps" :key="prop.name">
|
||||
<td class="relative flex-shrink-0">
|
||||
<code>{{ prop.name }}</code><span v-if="prop.required" class="font-bold text-red-500 dark:text-red-400 absolute top-0 ml-1">*</span>
|
||||
</td>
|
||||
<td>
|
||||
<code v-if="prop.default">{{ prop.default }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<a v-if="prop.name === 'ui'" href="#preset">
|
||||
<code>{{ prop.type }}</code>
|
||||
</a>
|
||||
<code v-else class="break-all">
|
||||
{{ prop.type }}
|
||||
</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<FieldGroup>
|
||||
<Field v-for="prop in metaProps" :key="prop.name" v-bind="prop">
|
||||
<code v-if="prop.default">{{ prop.default }}</code>
|
||||
|
||||
<Collapsible v-if="prop.schema?.kind === 'array' && prop.schema?.schema?.filter(schema => schema.kind === 'object').length">
|
||||
<FieldGroup v-for="schema in prop.schema.schema" :key="schema.name" class="!mt-0">
|
||||
<Field v-for="subProp in Object.values(schema.schema)" :key="(subProp as any).name" v-bind="subProp" />
|
||||
</FieldGroup>
|
||||
</Collapsible>
|
||||
<Collapsible v-else-if="prop.schema?.kind === 'object' && Object.values(prop.schema.schema)?.length">
|
||||
<FieldGroup class="!mt-0">
|
||||
<Field v-for="subProp in Object.values(prop.schema.schema)" :key="(subProp as any).name" v-bind="subProp" />
|
||||
</FieldGroup>
|
||||
</Collapsible>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
33
docs/components/content/examples/AccordionExampleBasic.vue
Normal file
33
docs/components/content/examples/AccordionExampleBasic.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
disabled: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Layouts',
|
||||
icon: 'i-heroicons-rectangle-group',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Components',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Utilities',
|
||||
icon: 'i-heroicons-wrench-screwdriver',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items" />
|
||||
</template>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Layouts',
|
||||
icon: 'i-heroicons-rectangle-group',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Components',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Utilities',
|
||||
icon: 'i-heroicons-wrench-screwdriver',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items" :ui="{ wrapper: 'flex flex-col w-full' }">
|
||||
<template #default="{ item, index, open }">
|
||||
<UButton color="gray" variant="ghost" class="border-b border-gray-200 dark:border-gray-700" :ui="{ rounded: 'rounded-none', padding: { sm: 'p-3' } }">
|
||||
<template #leading>
|
||||
<div class="w-6 h-6 rounded-full bg-primary-500 dark:bg-primary-400 flex items-center justify-center -my-1">
|
||||
<UIcon :name="item.icon" class="w-4 h-4 text-white dark:text-gray-900" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<span class="truncate">{{ index + 1 }}. {{ item.label }}</span>
|
||||
|
||||
<template #trailing>
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="w-5 h-5 ms-auto transform transition-transform duration-200"
|
||||
:class="[open && 'rotate-90']"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
slot: 'getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
defaultOpen: true,
|
||||
slot: 'installation'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
defaultOpen: true,
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Layouts',
|
||||
icon: 'i-heroicons-rectangle-group',
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Components',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Utilities',
|
||||
icon: 'i-heroicons-wrench-screwdriver',
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items">
|
||||
<template #item="{ item }">
|
||||
<p class="italic text-gray-900 dark:text-white text-center">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #getting-started>
|
||||
<div class="flex flex-col justify-center items-center gap-1">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
<span class="hidden sm:block">NuxtLabs</span><span class="sm:text-primary-500 dark:sm:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Fully styled and customizable components for Nuxt.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #installation="{ description }">
|
||||
<div class="flex flex-col justify-center items-center gap-1 mb-4">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
Installation
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Install <code>@nuxthq/ui</code> dependency to your project:
|
||||
</p>
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<code>$ npm install @nuxtlabs/ui</code>
|
||||
<code>$ nnpm install -D @nuxthq/ui</code>
|
||||
<code>$ pnpm i -D @nuxthq/ui</code>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</template>
|
||||
12
docs/components/content/examples/AlertExampleHtml.vue
Normal file
12
docs/components/content/examples/AlertExampleHtml.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<UAlert title="Heads <i>up</i>!" icon="i-heroicons-command-line">
|
||||
<template #title="{ title }">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
You can add <b>components</b> to your app using the <u>cli</u>.
|
||||
</template>
|
||||
</UAlert>
|
||||
</template>
|
||||
@@ -8,7 +8,7 @@ const groups = computed(() => {
|
||||
return []
|
||||
}
|
||||
|
||||
const users = await $fetch(`https://jsonplaceholder.typicode.com/users`, { params: { q } })
|
||||
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
}
|
||||
|
||||
@@ -18,17 +18,17 @@ const actions = [
|
||||
|
||||
const groups = computed(() => commandPaletteRef.value?.query
|
||||
? [{
|
||||
key: 'users',
|
||||
commands: users
|
||||
}]
|
||||
key: 'users',
|
||||
commands: users
|
||||
}]
|
||||
: [{
|
||||
key: 'recent',
|
||||
label: 'Recent searches',
|
||||
commands: users.slice(0, 1)
|
||||
}, {
|
||||
key: 'actions',
|
||||
commands: actions
|
||||
}])
|
||||
key: 'recent',
|
||||
label: 'Recent searches',
|
||||
commands: users.slice(0, 1)
|
||||
}, {
|
||||
key: 'actions',
|
||||
commands: actions
|
||||
}])
|
||||
|
||||
function onSelect (option) {
|
||||
if (option.click) {
|
||||
|
||||
47
docs/components/content/examples/DropdownExampleSlot.vue
Normal file
47
docs/components/content/examples/DropdownExampleSlot.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'ben@example.com',
|
||||
slot: 'account',
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Settings',
|
||||
icon: 'i-heroicons-cog-8-tooth'
|
||||
}], [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open'
|
||||
}, {
|
||||
label: 'Changelog',
|
||||
icon: 'i-heroicons-megaphone'
|
||||
}, {
|
||||
label: 'Status',
|
||||
icon: 'i-heroicons-signal'
|
||||
}], [{
|
||||
label: 'Sign out',
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle'
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :ui="{ item: { disabled: 'cursor-text select-text' } }" :popper="{ placement: 'bottom-start' }">
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" />
|
||||
|
||||
<template #account="{ item }">
|
||||
<div class="text-left">
|
||||
<p>
|
||||
Signed in as
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
|
||||
</template>
|
||||
</UDropdown>
|
||||
</template>
|
||||
44
docs/components/content/examples/FormExampleBasic.vue
Normal file
44
docs/components/content/examples/FormExampleBasic.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { FormError } from '@nuxthq/ui/dist/runtime/types'
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const validate = (state: any): FormError[] => {
|
||||
const errors = []
|
||||
if (!state.email) errors.push({ path: 'email', message: 'Required' })
|
||||
if (!state.password) errors.push({ path: 'password', message: 'Required' })
|
||||
return errors
|
||||
}
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:validate="validate"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
100
docs/components/content/examples/FormExampleElements.vue
Normal file
100
docs/components/content/examples/FormExampleElements.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { Form } from '@nuxthq/ui/dist/runtime/types'
|
||||
|
||||
const options = [
|
||||
{ label: 'Option 1', value: 'option-1' },
|
||||
{ label: 'Option 2', value: 'option-2' },
|
||||
{ label: 'Option 3', value: 'option-3' }
|
||||
]
|
||||
|
||||
const state = ref({
|
||||
input: undefined,
|
||||
textarea: undefined,
|
||||
select: undefined,
|
||||
selectMenu: undefined,
|
||||
checkbox: undefined,
|
||||
toggle: undefined,
|
||||
radio: undefined,
|
||||
switch: undefined,
|
||||
range: undefined
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string().min(10),
|
||||
textarea: z.string().min(10),
|
||||
select: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
selectMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
toggle: z.boolean().refine(value => value === true, {
|
||||
message: 'Toggle me'
|
||||
}),
|
||||
checkbox: z.boolean().refine(value => value === true, {
|
||||
message: 'Check me'
|
||||
}),
|
||||
radio: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
range: z.number().max(20, { message: 'Must be less than 20' })
|
||||
})
|
||||
|
||||
type Schema = z.infer<typeof schema>
|
||||
|
||||
const form = ref<Form<Schema>>()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup name="input" label="Input">
|
||||
<UInput v-model="state.input" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="textarea" label="Textarea">
|
||||
<UTextarea v-model="state.textarea" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="select" label="Select">
|
||||
<USelect v-model="state.select" placeholder="Select..." :options="options" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="selectMenu" label="Select Menu">
|
||||
<USelectMenu v-model="state.selectMenu" placeholder="Select..." :options="options" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="toggle" label="Toggle">
|
||||
<UToggle v-model="state.toggle" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="checkbox" label="Checkbox">
|
||||
<UCheckbox v-model="state.checkbox" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="radio" label="Radio">
|
||||
<URadio v-for="option in options" :key="option.value" v-model="state.radio" v-bind="option">
|
||||
{{ option.label }}
|
||||
</URadio>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="range" label="Range">
|
||||
<URange v-model="state.range" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
44
docs/components/content/examples/FormExampleJoi.vue
Normal file
44
docs/components/content/examples/FormExampleJoi.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Joi from 'joi'
|
||||
|
||||
const schema = Joi.object({
|
||||
emailJoi: Joi.string().required(),
|
||||
passwordJoi: Joi.string()
|
||||
.min(8)
|
||||
.required()
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="emailJoi">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="passwordJoi">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
47
docs/components/content/examples/FormExampleYup.vue
Normal file
47
docs/components/content/examples/FormExampleYup.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { object, string, InferType } from 'yup'
|
||||
import type { Form } from '@nuxthq/ui/dist/runtime/types'
|
||||
|
||||
const schema = object({
|
||||
emailYup: string().email('Invalid email').required('Required'),
|
||||
passwordYup: string()
|
||||
.min(8, 'Must be at least 8 characters')
|
||||
.required('Required')
|
||||
})
|
||||
|
||||
type Schema = InferType<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref<Form<Schema>>()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="emailYup">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="passwordYup">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
45
docs/components/content/examples/FormExampleZod.vue
Normal file
45
docs/components/content/examples/FormExampleZod.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { Form } from '@nuxthq/ui/dist/runtime/types'
|
||||
|
||||
const schema = z.object({
|
||||
emailZod: z.string().email('Invalid email'),
|
||||
passwordZod: z.string().min(8, 'Must be at least 8 characters')
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref<Form<Schema>>()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="emailZod">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="passwordZod">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen" :overlay="false">
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-48" />
|
||||
</div>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen" :transition="false">
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-48" />
|
||||
</div>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen" prevent-close>
|
||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
Modal
|
||||
</h3>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-32" />
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Notification <i>italic</i>', description: 'This is an <u>underlined</u> and <b>bold</b> notification.' })" />
|
||||
</template>
|
||||
@@ -4,8 +4,8 @@ const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full gap-3">
|
||||
<div class="flex justify-between w-full mb-2 border-b pb-4">
|
||||
<div class="w-full divide-y divide-gray-200 dark:divide-gray-700 space-y-4">
|
||||
<div class="flex 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 mt-4">
|
||||
<div class="flex justify-between w-full pt-4">
|
||||
<div dir="ltr">
|
||||
<UPagination
|
||||
v-model="page"
|
||||
11
docs/components/content/examples/PopoverExampleMode.vue
Normal file
11
docs/components/content/examples/PopoverExampleMode.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover mode="hover">
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-20 w-48" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
@@ -4,7 +4,8 @@ const countries = [{
|
||||
value: 'US'
|
||||
}, {
|
||||
name: 'Canada',
|
||||
value: 'CA'
|
||||
value: 'CA',
|
||||
disabled: true
|
||||
}, {
|
||||
name: 'Mexico',
|
||||
value: 'MX'
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
const search = async (q) => {
|
||||
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email })).filter(Boolean)
|
||||
}
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:searchable="search"
|
||||
placeholder="Search for a user..."
|
||||
multiple
|
||||
by="id"
|
||||
/>
|
||||
</template>
|
||||
@@ -5,22 +5,19 @@ const people = [{
|
||||
href: 'https://github.com/benjamincanac',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
|
||||
},
|
||||
{
|
||||
}, {
|
||||
id: 'Atinux',
|
||||
label: 'Atinux',
|
||||
href: 'https://github.com/Atinux',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
|
||||
},
|
||||
{
|
||||
}, {
|
||||
id: 'smarroufin',
|
||||
label: 'smarroufin',
|
||||
href: 'https://github.com/smarroufin',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
|
||||
},
|
||||
{
|
||||
}, {
|
||||
id: 'nobody',
|
||||
label: 'Nobody',
|
||||
icon: 'i-heroicons-user-circle'
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Wade Cooper'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Arlene Mccoy'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Devon Webb'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Tom Cook'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0].id)
|
||||
|
||||
const current = computed(() => people.find(person => person.id === selected.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:options="people"
|
||||
placeholder="Select people"
|
||||
value-attribute="id"
|
||||
option-attribute="name"
|
||||
>
|
||||
<template #label>
|
||||
{{ current.name }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -1,30 +0,0 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<div class="p-4 sm:p-6 flex flex-col flex-1 gap-4 sm:gap-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="font-semibold text-gray-900 dark:text-white">
|
||||
Title
|
||||
</h2>
|
||||
|
||||
<UButton
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
color="gray"
|
||||
variant="link"
|
||||
size="md"
|
||||
:padded="false"
|
||||
@click="isOpen = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Placeholder class="flex-1 w-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
15
docs/components/content/examples/SlideoverExampleBasic.vue
Normal file
15
docs/components/content/examples/SlideoverExampleBasic.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<div class="p-4 flex-1">
|
||||
<Placeholder class="h-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
23
docs/components/content/examples/SlideoverExampleCard.vue
Normal file
23
docs/components/content/examples/SlideoverExampleCard.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-full" />
|
||||
|
||||
<template #footer>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen" :overlay="false">
|
||||
<div class="p-4 flex-1">
|
||||
<Placeholder class="h-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen" :transition="false">
|
||||
<div class="p-4 flex-1">
|
||||
<Placeholder class="h-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen" prevent-close>
|
||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
Slideover
|
||||
</h3>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-full" />
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
245
docs/components/content/examples/TableExampleAdvanced.vue
Normal file
245
docs/components/content/examples/TableExampleAdvanced.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<script lang="ts" setup>
|
||||
// Columns
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: '#',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'completed',
|
||||
label: 'Status',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'actions',
|
||||
label: 'Actions',
|
||||
sortable: false
|
||||
}]
|
||||
|
||||
const selectedColumns = ref(columns)
|
||||
const columnsTable = computed(() => columns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
// Selected Rows
|
||||
const selectedRows = ref([])
|
||||
|
||||
function select (row) {
|
||||
const index = selectedRows.value.findIndex((item) => item.id === row.id)
|
||||
if (index === -1) {
|
||||
selectedRows.value.push(row)
|
||||
} else {
|
||||
selectedRows.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
const actions = [
|
||||
[{
|
||||
key: 'completed',
|
||||
label: 'Completed',
|
||||
icon: 'i-heroicons-check'
|
||||
}], [{
|
||||
key: 'uncompleted',
|
||||
label: 'In Progress',
|
||||
icon: 'i-heroicons-arrow-path'
|
||||
}]
|
||||
]
|
||||
|
||||
// Filters
|
||||
const todoStatus = [{
|
||||
key: 'uncompleted',
|
||||
label: 'In Progress',
|
||||
value: false
|
||||
}, {
|
||||
key: 'completed',
|
||||
label: 'Completed',
|
||||
value: true
|
||||
}]
|
||||
|
||||
const search = ref('')
|
||||
const selectedStatus = ref([])
|
||||
const searchStatus = computed(() => {
|
||||
if (selectedStatus.value?.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (selectedStatus?.value?.length > 1) {
|
||||
return `?completed=${selectedStatus.value[0].value}&completed=${selectedStatus.value[1].value}`
|
||||
}
|
||||
|
||||
return `?completed=${selectedStatus.value[0].value}`
|
||||
})
|
||||
|
||||
const resetFilters = () => {
|
||||
search.value = ''
|
||||
selectedStatus.value = []
|
||||
}
|
||||
|
||||
// Pagination
|
||||
const page = ref(1)
|
||||
const pageCount = ref(10)
|
||||
const pageTotal = ref(200) // This value should be dynamic coming from the API
|
||||
const pageFrom = computed(() => (page.value - 1) * pageCount.value + 1)
|
||||
const pageTo = computed(() => Math.min(page.value * pageCount.value, pageTotal.value))
|
||||
|
||||
// Data
|
||||
const { data: todos, pending } = await useLazyAsyncData('todos', () => $fetch<{
|
||||
id: number
|
||||
title: string
|
||||
completed: string
|
||||
}[]>(`https://jsonplaceholder.typicode.com/todos${searchStatus.value}`, {
|
||||
query: {
|
||||
q: search.value,
|
||||
'_page': page.value,
|
||||
'_limit': pageCount.value
|
||||
}
|
||||
}), {
|
||||
default: () => [],
|
||||
watch: [page, search, searchStatus, pageCount]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard
|
||||
class="w-full"
|
||||
:ui="{
|
||||
base: '',
|
||||
ring: '',
|
||||
divide: 'divide-y divide-gray-200 dark:divide-gray-700',
|
||||
header: { padding: 'px-4 py-5' },
|
||||
body: { padding: '', base: 'divide-y divide-gray-200 dark:divide-gray-700' },
|
||||
footer: { padding: 'p-4' }
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-900 dark:text-white leading-tight">
|
||||
Todos
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="flex items-center justify-between gap-3 px-4 py-3">
|
||||
<UInput v-model="search" icon="i-heroicons-magnifying-glass-20-solid" placeholder="Search..." />
|
||||
|
||||
<USelectMenu v-model="selectedStatus" :options="todoStatus" multiple placeholder="Status" class="w-40" />
|
||||
</div>
|
||||
|
||||
<!-- Header and Action buttons -->
|
||||
<div class="flex justify-between items-center w-full px-4 py-3">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-sm leading-5">Rows per page:</span>
|
||||
|
||||
<USelect
|
||||
v-model="pageCount"
|
||||
:options="[3, 5, 10, 20, 30, 40]"
|
||||
class="me-2 w-20"
|
||||
size="xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1.5 items-center">
|
||||
<UDropdown v-if="selectedRows.length > 1" :items="actions" :ui="{ width: 'w-36' }">
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-down"
|
||||
trailing
|
||||
variant="soft"
|
||||
size="xs"
|
||||
>
|
||||
Mark as
|
||||
</UButton>
|
||||
</UDropdown>
|
||||
|
||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple>
|
||||
<UButton
|
||||
icon="i-heroicons-view-columns"
|
||||
variant="soft"
|
||||
size="xs"
|
||||
>
|
||||
Columns
|
||||
</UButton>
|
||||
</USelectMenu>
|
||||
|
||||
<UButton
|
||||
icon="i-heroicons-funnel"
|
||||
variant="soft"
|
||||
color="red"
|
||||
size="xs"
|
||||
:disabled="search === '' && selectedStatus.length === 0"
|
||||
@click="resetFilters"
|
||||
>
|
||||
Reset
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<UTable
|
||||
v-model="selectedRows"
|
||||
:rows="todos"
|
||||
:columns="columnsTable"
|
||||
:loading="pending"
|
||||
sort-asc-icon="i-heroicons-arrow-up"
|
||||
sort-desc-icon="i-heroicons-arrow-down"
|
||||
@select="select"
|
||||
>
|
||||
<template #completed-data="{ row }">
|
||||
<UBadge size="xs" :label="row.completed ? 'Completed' : 'In Progress'" :color="row.completed ? 'emerald' : 'orange'" variant="subtle" />
|
||||
</template>
|
||||
|
||||
<template #actions-data="{ row }">
|
||||
<UButton
|
||||
v-if="!row.completed"
|
||||
icon="i-heroicons-check"
|
||||
size="2xs"
|
||||
color="emerald"
|
||||
variant="outline"
|
||||
:ui="{ rounded: 'rounded-full' }"
|
||||
square
|
||||
/>
|
||||
|
||||
<UButton
|
||||
v-else
|
||||
icon="i-heroicons-arrow-path"
|
||||
size="2xs"
|
||||
color="orange"
|
||||
variant="outline"
|
||||
:ui="{ rounded: 'rounded-full' }"
|
||||
square
|
||||
/>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
<!-- Number of rows & Pagination -->
|
||||
<template #footer>
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<span class="text-sm leading-5">
|
||||
Showing
|
||||
<span class="font-medium">{{ pageFrom }}</span>
|
||||
to
|
||||
<span class="font-medium">{{ pageTo }}</span>
|
||||
of
|
||||
<span class="font-medium">{{ pageTotal }}</span>
|
||||
results
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<UPagination
|
||||
v-model="page"
|
||||
:page-count="pageCount"
|
||||
:total="pageTotal"
|
||||
:ui="{
|
||||
wrapper: 'flex items-center gap-1',
|
||||
rounded: 'rounded-full min-w-[32px] justify-center',
|
||||
default: {
|
||||
activeButton: {
|
||||
variant: 'outline'
|
||||
}
|
||||
}
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
54
docs/components/content/examples/TableExampleClickable.vue
Normal file
54
docs/components/content/examples/TableExampleClickable.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}]
|
||||
|
||||
function select (row) {
|
||||
const index = selected.value.findIndex((item) => item.id === row.id)
|
||||
if (index === -1) {
|
||||
selected.value.push(row)
|
||||
} else {
|
||||
selected.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" @select="select" />
|
||||
</template>
|
||||
17
docs/components/content/examples/TabsExampleBasic.vue
Normal file
17
docs/components/content/examples/TabsExampleBasic.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Tab1',
|
||||
content: 'This is the content shown for Tab1'
|
||||
}, {
|
||||
label: 'Tab2',
|
||||
disabled: true,
|
||||
content: 'And, this is the content for Tab2'
|
||||
}, {
|
||||
label: 'Tab3',
|
||||
content: 'Finally, this is the content for Tab3'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" />
|
||||
</template>
|
||||
17
docs/components/content/examples/TabsExampleCard.vue
Normal file
17
docs/components/content/examples/TabsExampleCard.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Tab1',
|
||||
content: 'This is the content shown for Tab1'
|
||||
}, {
|
||||
label: 'Tab2',
|
||||
disabled: true,
|
||||
content: 'And, this is the content for Tab2'
|
||||
}, {
|
||||
label: 'Tab3',
|
||||
content: 'Finally, this is the content for Tab3'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" />
|
||||
</template>
|
||||
29
docs/components/content/examples/TabsExampleDefaultSlot.vue
Normal file
29
docs/components/content/examples/TabsExampleDefaultSlot.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
content: 'This is the content shown for Tab1'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
content: 'And, this is the content for Tab2'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
content: 'Finally, this is the content for Tab3'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" class="w-full">
|
||||
<template #default="{ item, index, selected }">
|
||||
<div class="flex items-center gap-2 relative truncate">
|
||||
<UIcon :name="item.icon" class="w-4 h-4 flex-shrink-0" />
|
||||
|
||||
<span class="truncate">{{ index + 1 }}. {{ item.label }}</span>
|
||||
|
||||
<span v-if="selected" class="absolute -right-4 w-2 h-2 rounded-full bg-primary-500 dark:bg-primary-400" />
|
||||
</div>
|
||||
</template>
|
||||
</UTabs>
|
||||
</template>
|
||||
16
docs/components/content/examples/TabsExampleIndex.vue
Normal file
16
docs/components/content/examples/TabsExampleIndex.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Tab1',
|
||||
content: 'This is the content shown for Tab1'
|
||||
}, {
|
||||
label: 'Tab2',
|
||||
content: 'And, this is the content for Tab2'
|
||||
}, {
|
||||
label: 'Tab3',
|
||||
content: 'Finally, this is the content for Tab3'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" :default-index="2" />
|
||||
</template>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
slot: 'account',
|
||||
label: 'Account'
|
||||
}, {
|
||||
slot: 'password',
|
||||
label: 'Password'
|
||||
}]
|
||||
|
||||
const accountForm = reactive({ name: 'Benjamin', username: 'benjamincanac' })
|
||||
const passwordForm = reactive({ currentPassword: '', newPassword: '' })
|
||||
|
||||
function onSubmitAccount () {
|
||||
console.log('Submitted form:', accountForm)
|
||||
}
|
||||
|
||||
function onSubmitPassword () {
|
||||
console.log('Submitted form:', passwordForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" class="w-full">
|
||||
<template #account="{ item }">
|
||||
<UCard @submit.prevent="onSubmitAccount">
|
||||
<template #header>
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<UFormGroup label="Name" name="name" class="mb-3">
|
||||
<UInput v-model="accountForm.name" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Username" name="username">
|
||||
<UInput v-model="accountForm.username" />
|
||||
</UFormGroup>
|
||||
|
||||
<template #footer>
|
||||
<UButton type="submit" color="black">
|
||||
Save account
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<template #password="{ item }">
|
||||
<UCard @submit.prevent="onSubmitPassword">
|
||||
<template #header>
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Change your password here. After saving, you'll be logged out.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<UFormGroup label="Current Password" name="current" required class="mb-3">
|
||||
<UInput v-model="passwordForm.currentPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="New Password" name="new" required>
|
||||
<UInput v-model="passwordForm.newPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
|
||||
<template #footer>
|
||||
<UButton type="submit" color="black">
|
||||
Save password
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
</UTabs>
|
||||
</template>
|
||||
58
docs/components/content/examples/TabsExampleItemSlot.vue
Normal file
58
docs/components/content/examples/TabsExampleItemSlot.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
key: 'account',
|
||||
label: 'Account',
|
||||
description: 'Make changes to your account here. Click save when you\'re done.'
|
||||
}, {
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
description: 'Change your password here. After saving, you\'ll be logged out.'
|
||||
}]
|
||||
|
||||
const accountForm = reactive({ name: 'Benjamin', username: 'benjamincanac' })
|
||||
const passwordForm = reactive({ currentPassword: '', newPassword: '' })
|
||||
|
||||
function onSubmit (form) {
|
||||
console.log('Submitted form:', form)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" class="w-full">
|
||||
<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">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div v-if="item.key === 'account'" class="space-y-3">
|
||||
<UFormGroup label="Name" name="name">
|
||||
<UInput v-model="accountForm.name" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Username" name="username">
|
||||
<UInput v-model="accountForm.username" />
|
||||
</UFormGroup>
|
||||
</div>
|
||||
<div v-else-if="item.key === 'password'" class="space-y-3">
|
||||
<UFormGroup label="Current Password" name="current" required>
|
||||
<UInput v-model="passwordForm.currentPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="New Password" name="new" required>
|
||||
<UInput v-model="passwordForm.newPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<UButton type="submit" color="black">
|
||||
Save {{ item.key === 'account' ? 'account' : 'password' }}
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
</UTabs>
|
||||
</template>
|
||||
16
docs/components/content/examples/TabsExampleVertical.vue
Normal file
16
docs/components/content/examples/TabsExampleVertical.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Tab1',
|
||||
content: 'This is the content shown for Tab1'
|
||||
}, {
|
||||
label: 'Tab2',
|
||||
content: 'And, this is the content for Tab2'
|
||||
}, {
|
||||
label: 'Tab3',
|
||||
content: 'Finally, this is the content for Tab3'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" orientation="vertical" :ui="{ wrapper: 'flex items-center gap-4', list: { width: 'w-48' } }" />
|
||||
</template>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
},
|
||||
label: 'Benjamin Canac'
|
||||
}, {
|
||||
label: 'KeJun'
|
||||
}]
|
||||
|
||||
const { ui } = useAppConfig()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #avatar="{ link }">
|
||||
<UAvatar
|
||||
v-if="link.avatar"
|
||||
v-bind="{ size: ui.verticalNavigation.avatar.size, ...link.avatar }"
|
||||
:class="[ui.verticalNavigation.avatar.base]"
|
||||
/>
|
||||
<UIcon v-else name="i-heroicons-user-circle-20-solid" class="text-lg" />
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: '.github',
|
||||
icon: 'i-heroicons-folder-20-solid',
|
||||
badge: 'chore(github): use pnpm 8',
|
||||
time: 'last month'
|
||||
}, {
|
||||
label: '.editorconfig',
|
||||
icon: 'i-heroicons-document-solid',
|
||||
badge: 'Initial commit',
|
||||
time: '2 years ago'
|
||||
}, {
|
||||
label: '.package.json',
|
||||
icon: 'i-heroicons-document-solid',
|
||||
badge: 'chore(deps): bump',
|
||||
time: '16 hours ago'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
class="w-full"
|
||||
:ui="{
|
||||
label: 'truncate relative text-gray-900 dark:text-white flex-initial w-32 text-left'
|
||||
}"
|
||||
>
|
||||
<template #badge="{ link }">
|
||||
<div class="flex-1 flex justify-between relative truncate">
|
||||
<div>{{ link.badge }}</div>
|
||||
<div>{{ link.time }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Navigation',
|
||||
children: [{
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
}, {
|
||||
label: 'Data',
|
||||
children: [{
|
||||
label: 'Table',
|
||||
to: '/data/table'
|
||||
}]
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #default="{ link }">
|
||||
<div class="relative text-left w-full">
|
||||
<div class="mb-2">
|
||||
{{ link.label }}
|
||||
</div>
|
||||
<UVerticalNavigation v-if="link.children" :links="link.children" />
|
||||
</div>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
const types = {
|
||||
bug: {
|
||||
icon: 'i-heroicons-bug-ant-20-solid',
|
||||
color: 'text-red-500'
|
||||
},
|
||||
docs: {
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
color: 'text-blue-500'
|
||||
},
|
||||
lock: {
|
||||
icon: 'i-heroicons-lock-closed-20-solid',
|
||||
color: 'text-gray dark:text-white'
|
||||
},
|
||||
default: {
|
||||
icon: 'i-heroicons-question-mark-circle-20-solid',
|
||||
color: 'text-green-500'
|
||||
}
|
||||
}
|
||||
const links = [{
|
||||
label: 'UDropdown and UPopover dropdown menu, dropdown will be obscured',
|
||||
type: 'bug'
|
||||
}, {
|
||||
label: 'Uncaught (in promise) ReferenceError: ref is not defined',
|
||||
type: 'lock'
|
||||
}, {
|
||||
label: 'Fully styled and customizable components for Nuxt.',
|
||||
type: 'docs'
|
||||
}, {
|
||||
label: 'Can I pass a tailwind color to UNotifications with `toast.add()` ?'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #icon="{ link }">
|
||||
<UIcon v-if="link.type" :name="types[link.type].icon" :class="types[link.type].color" class="text-base" />
|
||||
<UIcon v-else :name="types.default.icon" :class="types.default.color" class="text-base" />
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
@@ -1,71 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '#imports'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
highlights: {
|
||||
type: Array as () => number[],
|
||||
default: () => []
|
||||
},
|
||||
meta: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
const clipboard = useCopyToClipboard({ timeout: 2000 })
|
||||
const icon = ref('i-heroicons-clipboard-document')
|
||||
|
||||
function copy () {
|
||||
clipboard.copy(props.code, { title: 'Copied to clipboard!' })
|
||||
|
||||
icon.value = 'i-heroicons-clipboard-document-check'
|
||||
|
||||
setTimeout(() => {
|
||||
icon.value = 'i-heroicons-clipboard-document'
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
return {
|
||||
icon,
|
||||
copy
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="group relative" :class="`language-${language}`">
|
||||
<UButton
|
||||
:icon="icon"
|
||||
variant="solid"
|
||||
class="absolute right-3 top-3 opacity-0 group-hover:opacity-100 transition-opacity z-[1]"
|
||||
size="xs"
|
||||
tabindex="-1"
|
||||
@click="copy"
|
||||
/>
|
||||
|
||||
<span v-if="filename" class="text-gray-400 dark:text-gray-500 absolute right-3 bottom-3 text-sm group-hover:opacity-0 transition-opacity">{{ filename }}</span>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
pre code .line {
|
||||
display: block;
|
||||
min-height: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ id: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 :id="id" class="scroll-mt-[161px] lg:scroll-mt-[112px]">
|
||||
<NuxtLink :href="`#${id}`" class="group">
|
||||
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
|
||||
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</h2>
|
||||
</template>
|
||||
@@ -1,15 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ id: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3 :id="id" class="scroll-mt-[145px] lg:scroll-mt-[96px]">
|
||||
<NuxtLink :href="`#${id}`" class="group">
|
||||
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
|
||||
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</h3>
|
||||
</template>
|
||||
@@ -1,15 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ id: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3 :id="id" class="scroll-mt-[145px] lg:scroll-mt-[96px]">
|
||||
<NuxtLink :href="`#${id}`" class="group">
|
||||
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
|
||||
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</h3>
|
||||
</template>
|
||||
@@ -1,14 +1,14 @@
|
||||
<script setup>
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const navigation = inject('navigation')
|
||||
const links = inject('links')
|
||||
|
||||
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
|
||||
|
||||
const groups = computed(() => navigation.value.map(item => ({
|
||||
key: item._path,
|
||||
label: item.title,
|
||||
commands: files.value.filter(file => file._path.startsWith(item._path)).map(file => ({
|
||||
const groups = computed(() => links.value.map(item => ({
|
||||
key: item.to,
|
||||
label: item.label,
|
||||
commands: files.value.filter(file => file._path.startsWith(item.to)).map(file => ({
|
||||
id: file._id,
|
||||
icon: 'i-heroicons-document',
|
||||
title: file.navigation?.title || file.title,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<aside class="hidden py-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-65px)] lg:sticky lg:pr-8 lg:pl-[2px]">
|
||||
<div class="relative">
|
||||
<!-- <div class="sticky top-0 pointer-events-none z-[1]">
|
||||
<div class="h-8 bg-white dark:bg-gray-900" />
|
||||
<div class="bg-white dark:bg-gray-900 relative pointer-events-auto">
|
||||
<DocsSearchButton class="w-full" />
|
||||
</div>
|
||||
<div class="h-8 bg-gradient-to-b from-white dark:from-gray-900" />
|
||||
</div> -->
|
||||
|
||||
<DocsAsideLinks />
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<div v-for="(group, index) in navigation" :key="index" class="space-y-3">
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-gray-200 truncate leading-6">
|
||||
{{ group.title }}
|
||||
</p>
|
||||
|
||||
<UVerticalNavigation
|
||||
:links="mapContentLinks(group.children)"
|
||||
class="mt-1"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6 flex items-center gap-2',
|
||||
padding: 'pl-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
>
|
||||
<template #badge="{ link }">
|
||||
<UBadge v-if="link.badge" size="xs" :ui="{ rounded: 'rounded-full' }">
|
||||
{{ link.badge }}
|
||||
</UBadge>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const navigation: Ref<NavItem[]> = inject('navigation')
|
||||
|
||||
function mapContentLinks (links: NavItem[]) {
|
||||
return links?.map(link => ({ label: link.title, icon: link.icon, to: link._path, badge: link.badge })) || []
|
||||
}
|
||||
</script>
|
||||
@@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<footer class="flex items-center justify-between gap-1.5">
|
||||
<div class="flex items-baseline gap-1.5 text-sm text-center text-gray-500 dark:text-gray-400">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs">
|
||||
<LogoLabs class="text-primary-500 w-14 h-auto dark:text-primary-400" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<NuxtLink to="https://github.com/nuxtlabs/ui/releases" target="_blank">
|
||||
<UBadge label="v2.4.0" />
|
||||
</NuxtLink>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between gap-1.5">
|
||||
<UButton
|
||||
v-if="page"
|
||||
:to="`https://github.com/nuxtlabs/ui/edit/dev/docs/content/${page._file}`"
|
||||
label="Edit this page on GitHub"
|
||||
color="primary"
|
||||
variant="link"
|
||||
:padded="false"
|
||||
icon="i-heroicons-pencil-square"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
page: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<header v-if="page" class="relative border-b border-gray-200 dark:border-gray-800 pb-8 mb-12">
|
||||
<p class="mb-4 text-sm leading-6 font-semibold text-primary-500 dark:text-primary-400 capitalize">
|
||||
{{ page._dir?.title ? page._dir.title : useLowerCase(page._dir) }}
|
||||
</p>
|
||||
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h1 class="text-3xl sm:text-4xl font-extrabold text-gray-900 tracking-tight dark:text-white">
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
|
||||
<div v-if="page.headlessui || page.github" class="flex items-center gap-2 mt-4 lg:mt-0">
|
||||
<UButton
|
||||
v-if="page.headlessui"
|
||||
:label="page.headlessui.label"
|
||||
:to="page.headlessui.to"
|
||||
icon="i-simple-icons-headlessui"
|
||||
color="white"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
v-if="page.github"
|
||||
label="GitHub"
|
||||
icon="i-simple-icons-github"
|
||||
color="white"
|
||||
:to="`https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/${page._dir}/${page.title.replace(' ', '')}${page.github.suffix || '.vue'}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="page.description" class="mt-4 text-lg text-gray-500 dark:text-gray-400">
|
||||
{{ page.description }}
|
||||
</p>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
page: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<DocsPrevNextCard
|
||||
v-if="prev"
|
||||
:title="prev.title"
|
||||
:description="prev.description"
|
||||
:to="prev._path"
|
||||
icon="i-heroicons-arrow-left-20-solid"
|
||||
/>
|
||||
<span v-else class="hidden sm:block"> </span>
|
||||
<DocsPrevNextCard
|
||||
v-if="next"
|
||||
:title="next.title"
|
||||
:description="next.description"
|
||||
:to="next._path"
|
||||
icon="i-heroicons-arrow-right-20-solid"
|
||||
class="text-right"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
prev: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
next: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<NuxtLink :to="to" class="block px-5 py-8 border not-prose rounded-lg border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50 group">
|
||||
<div v-if="icon" class="inline-flex items-center rounded-full p-1.5 bg-gray-50 dark:bg-gray-800 group-hover:bg-primary-50 dark:group-hover:bg-primary-400/10 ring-1 ring-gray-300 dark:ring-gray-700 mb-4 group-hover:ring-primary-500/50 dark:group-hover:ring-primary-400/50">
|
||||
<UIcon :name="icon" class="w-5 h-5 text-gray-900 dark:text-gray-100 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<p class="text-gray-900 dark:text-gray-50 font-medium text-[15px] mb-1">
|
||||
{{ title }}
|
||||
</p>
|
||||
|
||||
<p class="text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
{{ description }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,176 +0,0 @@
|
||||
<template>
|
||||
<UModal
|
||||
v-model="isSearchModalOpen"
|
||||
:ui="{
|
||||
padding: 'sm:p-4',
|
||||
rounded: 'sm:rounded-lg',
|
||||
width: 'sm:max-w-3xl',
|
||||
height: 'h-screen sm:h-[28rem]'
|
||||
}"
|
||||
>
|
||||
<UCommandPalette
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
command-attribute="title"
|
||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'ghost', size: 'sm', class: '-mr-1.5' }"
|
||||
:ui="{ input: { height: 'h-16 sm:h-12', icon: { size: 'h-5 w-5', padding: 'pl-11' } } }"
|
||||
:fuse="{
|
||||
fuseOptions: { ignoreLocation: true, includeMatches: true, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
|
||||
resultLimit: 10
|
||||
}"
|
||||
@update:model-value="onSelect"
|
||||
@close="isSearchModalOpen = false"
|
||||
/>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
import type { Command } from '../../../src/runtime/types'
|
||||
|
||||
const navigation: Ref<NavItem[]> = inject('navigation')
|
||||
|
||||
const router = useRouter()
|
||||
const { usingInput } = useShortcuts()
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
|
||||
const commandPaletteRef = ref<HTMLElement & { query: Ref<string>, results: { item: Command }[] }>()
|
||||
|
||||
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
|
||||
|
||||
// Computed
|
||||
|
||||
const defaultGroups = computed(() => navigation.value.map(item => ({
|
||||
key: item._path,
|
||||
label: item.title,
|
||||
commands: files.value.filter(file => file._path.startsWith(item._path)).map(file => ({
|
||||
id: file._id,
|
||||
title: file.navigation?.title || file.title,
|
||||
to: file._path,
|
||||
suffix: file.description,
|
||||
icon: file.icon
|
||||
}))
|
||||
})))
|
||||
|
||||
const queryGroups = computed(() => navigation.value.map(item => ({
|
||||
key: item._path,
|
||||
label: item.title,
|
||||
commands: files.value.filter(file => file._path.startsWith(item._path)).flatMap((file) => {
|
||||
return [{
|
||||
id: file._id,
|
||||
title: file.navigation?.title || file.title,
|
||||
to: file._path,
|
||||
description: file.description,
|
||||
icon: file.icon
|
||||
},
|
||||
// @ts-ignore
|
||||
...Object.entries(groupByHeading(file.body.children)).map(([hash, { title, children }]) => ({
|
||||
id: `${file._path}${hash}`,
|
||||
title,
|
||||
prefix: `${file.navigation?.title || file.title} ->`,
|
||||
prefixClass: 'text-gray-700 dark:text-gray-200',
|
||||
to: `${file._path}${hash}`,
|
||||
children: concatChildren(children),
|
||||
icon: file.icon
|
||||
}))]
|
||||
})
|
||||
})))
|
||||
|
||||
const groups = computed(() => commandPaletteRef.value?.query ? queryGroups.value : defaultGroups.value)
|
||||
|
||||
// avoid conflicts between multiple meta_k shortcuts
|
||||
const canToggleModal = computed(() => isSearchModalOpen.value || !usingInput.value)
|
||||
|
||||
// Methods
|
||||
|
||||
function remapChildren (children: any[]) {
|
||||
return children?.map((grandChild) => {
|
||||
if (['code-inline', 'em', 'a', 'strong'].includes(grandChild.tag)) {
|
||||
return { type: 'text', value: grandChild.children.find(child => child.type === 'text')?.value || '' }
|
||||
}
|
||||
|
||||
return grandChild
|
||||
})
|
||||
}
|
||||
|
||||
function concatChildren (children: any[]) {
|
||||
return children.map((child) => {
|
||||
if (['alert'].includes(child.tag)) {
|
||||
child.children = concatChildren(child.children)
|
||||
}
|
||||
if (child.tag === 'p') {
|
||||
child.children = remapChildren(child.children)
|
||||
|
||||
child.children = child.children?.reduce((acc, grandChild) => {
|
||||
if (grandChild.type === 'text') {
|
||||
if (acc.length && acc[acc.length - 1].type === 'text') {
|
||||
acc[acc.length - 1].value += grandChild.value
|
||||
} else {
|
||||
acc.push(grandChild)
|
||||
}
|
||||
} else {
|
||||
acc.push(grandChild)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
if (['style'].includes(child.tag)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return child
|
||||
})
|
||||
}
|
||||
|
||||
function groupByHeading (children: any[]) {
|
||||
const groups = {} // grouped by path
|
||||
let hash = '' // file.page with potential `#anchor` concat
|
||||
let title: string | null
|
||||
for (const node of children) {
|
||||
// if heading found, udpate current path
|
||||
if (['h2', 'h3'].includes(node.tag)) {
|
||||
// find heading text value
|
||||
title = node.children?.find(child => child.type === 'text')?.value
|
||||
if (title) {
|
||||
hash = `#${node.props.id}`
|
||||
}
|
||||
}
|
||||
// push to existing/new group based on path
|
||||
if (groups[hash]) {
|
||||
groups[hash].children.push(node)
|
||||
} else {
|
||||
groups[hash] = { children: [node], title }
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
function onSelect (option) {
|
||||
isSearchModalOpen.value = false
|
||||
|
||||
if (option.click) {
|
||||
option.click()
|
||||
} else if (option.to) {
|
||||
router.push(option.to)
|
||||
} else if (option.href) {
|
||||
window.open(option.href, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
// Shortcuts
|
||||
|
||||
defineShortcuts({
|
||||
meta_k: {
|
||||
usingInput: true,
|
||||
whenever: [canToggleModal],
|
||||
handler: () => {
|
||||
isSearchModalOpen.value = !isSearchModalOpen.value
|
||||
}
|
||||
},
|
||||
escape: {
|
||||
usingInput: true,
|
||||
whenever: [isSearchModalOpen],
|
||||
handler: () => { isSearchModalOpen.value = false }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<UButton
|
||||
color="white"
|
||||
variant="outline"
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
label="Search..."
|
||||
truncate
|
||||
:ui="{
|
||||
color: {
|
||||
white: {
|
||||
outline: 'ring-1 ring-inset ring-gray-200 dark:ring-gray-800 hover:ring-gray-300 dark:hover:ring-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/50 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
|
||||
}
|
||||
}
|
||||
}"
|
||||
@click="isSearchModalOpen = true"
|
||||
>
|
||||
<template #trailing>
|
||||
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1 flex-shrink-0">
|
||||
<UKbd class="!text-inherit">
|
||||
{{ metaSymbol }}
|
||||
</UKbd>
|
||||
<UKbd class="!text-inherit">
|
||||
K
|
||||
</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div class="sticky top-16 bg-white/75 dark:bg-gray-900/75 backdrop-blur group lg:self-start -mx-4 sm:-mx-6 lg:mx-0 px-4 sm:px-6 lg:pl-8 lg:pr-0">
|
||||
<div class="py-3 lg:py-8 border-b border-dashed border-gray-200 dark:border-gray-800 lg:border-0">
|
||||
<button class="flex items-center gap-2" tabindex="-1" @click="isTocOpen = !isTocOpen">
|
||||
<span class="text-sm text-slate-900 font-semibold text-sm leading-6 dark:text-slate-100 truncate">Table of Contents</span>
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="lg:hidden w-4 h-4 transition-transform duration-100 transform text-gray-400 dark:text-gray-500" :class="[isTocOpen ? 'rotate-90' : 'rotate-0']" />
|
||||
</button>
|
||||
|
||||
<DocsTocLinks class="mt-2 lg:mt-4" :links="toc.links" :class="[isTocOpen ? 'lg:block' : 'hidden lg:block']" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
toc: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const isTocOpen = ref(false)
|
||||
</script>
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="link in links" :key="link.text" :class="{ 'ml-3': link.depth === 3 }">
|
||||
<a
|
||||
:href="`#${link.id}`"
|
||||
class="block py-1 font-medium text-sm"
|
||||
:class="[activeHeadings.includes(link.id) ? 'text-primary-500 dark:text-primary-400' : 'hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300']"
|
||||
@click.prevent="scrollToHeading(link.id)"
|
||||
>
|
||||
{{ link.text }}
|
||||
</a>
|
||||
|
||||
<DocsTocLinks v-if="link.children" :links="link.children" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TocLink } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
defineProps({
|
||||
links: {
|
||||
type: Array as PropType<TocLink[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['move'])
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { activeHeadings, updateHeadings } = useScrollspy()
|
||||
|
||||
watch(() => route.path, () => {
|
||||
setTimeout(() => {
|
||||
if (process.client) {
|
||||
updateHeadings([
|
||||
...document.querySelectorAll('h2'),
|
||||
...document.querySelectorAll('h3')
|
||||
])
|
||||
}
|
||||
}, 300)
|
||||
}, { immediate: true })
|
||||
|
||||
const scrollToHeading = (id: string) => {
|
||||
router.push(`#${id}`)
|
||||
emit('move', id)
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +0,0 @@
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
|
||||
const _useDocs = () => {
|
||||
const isSearchModalOpen = ref(false)
|
||||
|
||||
return {
|
||||
isSearchModalOpen
|
||||
}
|
||||
}
|
||||
|
||||
export const useDocs = createSharedComposable(_useDocs)
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Scrollspy allows you to watch visible headings in a specific page.
|
||||
* Useful for table of contents live style updates.
|
||||
*/
|
||||
export const useScrollspy = () => {
|
||||
const observer = ref() as Ref<IntersectionObserver>
|
||||
const visibleHeadings = ref([]) as Ref<string[]>
|
||||
const activeHeadings = ref([]) as Ref<string[]>
|
||||
|
||||
const observerCallback = (entries: IntersectionObserverEntry[]) =>
|
||||
entries.forEach((entry) => {
|
||||
const id = entry.target.id
|
||||
|
||||
if (entry.isIntersecting) { visibleHeadings.value.push(id) } else { visibleHeadings.value = visibleHeadings.value.filter(t => t !== id) }
|
||||
})
|
||||
|
||||
const updateHeadings = (headings: Element[]) =>
|
||||
headings.forEach((heading) => {
|
||||
observer.value.observe(heading)
|
||||
})
|
||||
|
||||
watch(visibleHeadings, (val, oldVal) => {
|
||||
if (val.length === 0) { activeHeadings.value = oldVal } else { activeHeadings.value = val }
|
||||
})
|
||||
|
||||
// Create intersection observer
|
||||
onBeforeMount(() => (observer.value = new IntersectionObserver(observerCallback)))
|
||||
|
||||
// Destroy it
|
||||
onBeforeUnmount(() => observer.value?.disconnect())
|
||||
|
||||
return {
|
||||
visibleHeadings,
|
||||
activeHeadings,
|
||||
updateHeadings
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ head:
|
||||
|
||||
This module has been developed by the [NuxtLabs](https://nuxtlabs.com/) team for [Volta](https://volta.net) and [Nuxt Studio](https://nuxt.studio/), its goal is to provide everything related to UI when building a Nuxt app. This includes components, icons, colors, dark mode but also keyboard shortcuts.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
[Volta](https://volta.net/) entire UI is built with this module alongside the 50+ keyboard shortcuts defined.
|
||||
::
|
||||
|
||||
@@ -31,7 +31,3 @@ This module has been developed by the [NuxtLabs](https://nuxtlabs.com/) team for
|
||||
- [tailwindlabs/headlessui](https://github.com/tailwindlabs/headlessui)
|
||||
- [vueuse/vueuse](https://github.com/vueuse/vueuse)
|
||||
- [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons)
|
||||
|
||||
::alert{icon="i-heroicons-exclamation-triangle"}
|
||||
This documentation is still a work in progress and will be updated regularly.
|
||||
::
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
description: 'Learn how to install and configure the module in your Nuxt app.'
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Install `@nuxthq/ui` dependency to your project:
|
||||
|
||||
::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm i -D @nuxthq/ui
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add -D @nuxthq/ui
|
||||
```
|
||||
@@ -14,10 +20,6 @@ yarn add -D @nuxthq/ui
|
||||
npm install -D @nuxthq/ui
|
||||
```
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm i -D @nuxthq/ui
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
2. Add it to your `modules` section in your `nuxt.config`:
|
||||
@@ -30,21 +32,17 @@ export default defineNuxtConfig({
|
||||
|
||||
That's it! You can now use all the components and composables in your Nuxt app ✨
|
||||
|
||||
::alert{icon="i-heroicons-exclamation-triangle"}
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
As this module installs [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/) and [@nuxtjs/color-mode](https://color-mode.nuxtjs.org/) for you, you should remove them from your `modules` and `dependencies` if you've previously installed them manually.
|
||||
::
|
||||
|
||||
## Playground
|
||||
|
||||
:u-button{icon="i-simple-icons-stackblitz" label="Play on StackBlitz" size="lg" to="https://stackblitz.com/edit/nuxtlabs-ui?file=app.config.ts,app.vue" target="_blank"}
|
||||
|
||||
## IntelliSense
|
||||
|
||||
If you're using VSCode, you can install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension to get autocompletion for the classes.
|
||||
|
||||
You can read more on how to set it up on the [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/tailwind/editor-support) module documentation, but to summarize, you'll need to add the following to your `settings.json`:
|
||||
You can read more on how to set it up on the [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/tailwind/editor-support) module documentation, but to summarize, you'll need to add the following to your `.vscode/settings.json`:
|
||||
|
||||
```json [settings.json]
|
||||
```json [.vscode/settings.json]
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
@@ -67,20 +65,52 @@ export default <Partial<Config>> {
|
||||
}
|
||||
```
|
||||
|
||||
If you do so, you'll need to add the following to your `settings.json`:
|
||||
If you do so, you'll need to add the following to your `.vscode/settings.json`:
|
||||
|
||||
```json [settings.json]
|
||||
```json [.vscode/settings.json]
|
||||
{
|
||||
"tailwindCSS.experimental.configFile": "tailwind.config.ts"
|
||||
}
|
||||
```
|
||||
|
||||
Also, the extension won't work when writing classes in your `app.config.ts` by default. You can add the following to your `settings.json` to fix this:
|
||||
Note, the extension won't work when writing classes in your `app.config.ts` by default.
|
||||
|
||||
```json [settings.json]
|
||||
Also, you might want IntelliSense on objects in your SFC by prefixing with `/*ui*/`.
|
||||
|
||||
To enable these two features, you can add the following to your `.vscode/settings.json`:
|
||||
|
||||
```json [.vscode/settings.json]
|
||||
{
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
["ui:\\s*{([^)]*)\\s*}", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["/\\*ui\\*/\\s*{([^;]*)}", ":\\s*[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
An example SFC using IntelliSense (note the `/*ui*/` prefix, also works with `ref()`):
|
||||
|
||||
```vue [example.vue]
|
||||
<template>
|
||||
<UCard :ui="ui" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const ui = /*ui*/ {
|
||||
background: 'bg-white dark:bg-slate-900'
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
You can also add the following to your `.vscode/settings.json` to enable IntelliSense when using the `ui` prop:
|
||||
|
||||
```json [.vscode/settings.json]
|
||||
{
|
||||
"tailwindCSS.classAttributes": [
|
||||
"class",
|
||||
"className",
|
||||
"ngClass",
|
||||
"ui"
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -92,7 +122,19 @@ Also, the extension won't work when writing classes in your `app.config.ts` by d
|
||||
| `prefix` | `u` | Define the prefix of the imported components. |
|
||||
| `global` | `false` | Expose components globally. |
|
||||
| `icons` | `['heroicons']` | Icon collections to load. |
|
||||
| `safelistColors` | `['primary']` | Force safelisting of colors. |
|
||||
| `safelistColors` | `['primary']` | Force safelisting of colors to need be purged. |
|
||||
|
||||
Configure options in your `nuxt.config.ts` as such:
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxthq/ui'],
|
||||
ui: {
|
||||
global: true,
|
||||
icons: ['mdi', 'simple-icons']
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Edge
|
||||
|
||||
@@ -108,4 +150,4 @@ Update your `package.json` to the following:
|
||||
}
|
||||
```
|
||||
|
||||
Then run `npm install` or `yarn install`.
|
||||
Then run `pnpm install`, `yarn install` or `npm install`.
|
||||
|
||||
@@ -19,7 +19,7 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i-heroicons-swatch-20-solid" class="w-4 h-4 align-middle text-primary-500 dark:text-primary-400"} button in the header.
|
||||
::
|
||||
|
||||
@@ -29,11 +29,11 @@ To provide dynamic colors that can be changed at runtime, this module uses CSS v
|
||||
|
||||
Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it would conflict with the `primary` color defined by the module.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
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`.
|
||||
::
|
||||
|
||||
Components having a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style), [Input](/elements/input#style) (inherited in [Select](/forms/select) and [SelectMenu](/forms/select-menu)), [Radio](/forms/radio), [Checkbox](/forms/checkbox), [Toggle](/forms/toggle), [Range](/forms/range) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
|
||||
Components having a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style), [Input](/forms/input#style) (inherited in [Select](/forms/select) and [SelectMenu](/forms/select-menu)), [Radio](/forms/radio), [Checkbox](/forms/checkbox), [Toggle](/forms/toggle), [Range](/forms/range) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
|
||||
|
||||
Variant classes of those components are defined with a syntax like `bg-{color}-500 dark:bg-{color}-400` so they can be used with any color. However, this means that Tailwind will not find those classes and therefore will not generate the corresponding CSS.
|
||||
|
||||
@@ -73,7 +73,7 @@ All the components are styled with dark mode in mind.
|
||||
|
||||
Thanks to [Tailwind CSS dark mode](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) class strategy and the [@nuxtjs/color-mode](https://github.com/nuxt-modules/color-mode) module, you literally have nothing to do.
|
||||
|
||||
::alert{icon="i-heroicons-puzzle-piece"}
|
||||
::callout{icon="i-heroicons-puzzle-piece"}
|
||||
Learn how to build a color mode button in the [Examples](/getting-started/examples#color-mode-button) page.
|
||||
::
|
||||
|
||||
@@ -87,7 +87,7 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
If you're stuck in dark mode even after changing this setting, you might need to remove the `nuxt-color-mode` entry from your browser's local storage.
|
||||
::
|
||||
|
||||
@@ -115,7 +115,7 @@ Each component has a `ui` prop that allows you to customize everything specifica
|
||||
</template>
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
You can find the default classes for each component under the `Preset` section.
|
||||
::
|
||||
|
||||
@@ -165,7 +165,7 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Search the icon you want to use on https://icones.js.org built by [@antfu](https://github.com/antfu).
|
||||
::
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Shortcuts are displayed and styled through the [Kbd](/elements/kbd) component.
|
||||
</template>
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
You will have a preview of how shortcuts are rendered in each component page.
|
||||
::
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Examples
|
||||
description: Discover some real-life examples of components you can build.
|
||||
---
|
||||
|
||||
::alert{icon="i-heroicons-wrench-screwdriver"}
|
||||
::callout{icon="i-heroicons-wrench-screwdriver"}
|
||||
If you have any ideas of examples you'd like to see, please comment on [this issue](https://github.com/nuxtlabs/ui/issues/297).
|
||||
::
|
||||
|
||||
@@ -134,6 +134,23 @@ const label = computed(() => date.value.toLocaleDateString('en-us', { weekday: '
|
||||
```
|
||||
::
|
||||
|
||||
### Table
|
||||
|
||||
Here is an example of a Table component with all its features implemented.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-advanced
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
## Theming
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the components.
|
||||
@@ -153,7 +170,7 @@ padding: false
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -168,7 +185,7 @@ padding: false
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -250,9 +267,17 @@ const items = ref(Array(55))
|
||||
```
|
||||
::
|
||||
|
||||
### LTR and RTL
|
||||
## RTL Support
|
||||
|
||||
Here are some examples of how components look like in RTL mode.
|
||||
|
||||
### Pagination
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:l-t-r-and-r-t-l-theme
|
||||
:pagination-example-r-t-l
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/examples/PaginationExampleRTL.vue"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
287
docs/content/2.elements/1.accordion.md
Normal file
287
docs/content/2.elements/1.accordion.md
Normal file
@@ -0,0 +1,287 @@
|
||||
---
|
||||
description: Display togglable accordion panels.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Accordion.vue
|
||||
- label: Disclosure
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/disclosure'
|
||||
navigation:
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Pass an array to the `items` prop of the Accordion component. Each item can have any property from the [Button](/elements/button) component such as `label`, `icon`, `color`, `variant`, `size`, etc. but also:
|
||||
|
||||
- `slot` - A key to customize the item with a slot.
|
||||
- `content` - The content to display in the panel by default.
|
||||
- `disabled` - Determines whether the item is disabled or not.
|
||||
- `defaultOpen` - Determines whether the item is initially open or closed.
|
||||
- `closeOthers` - Determines whether the item click close others or not. **It only works with multiple mode**.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:accordion-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
disabled: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, ...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
You can also pass any prop from the [Button](/elements/button) component directly to the Accordion component to style the buttons.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
items:
|
||||
- label: '1. What is NuxtLabs UI?'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: '2. Getting Started'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: '3. Theming'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: '4. Components'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'soft'
|
||||
size: 'sm'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
outline: 1
|
||||
ghost: 1
|
||||
soft: 1
|
||||
link: 1
|
||||
size:
|
||||
2xs: ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `open-icon` and `close-icon` props by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.accordion.default.openIcon` and `ui.accordion.default.closeIcon`.
|
||||
|
||||
You can also set them to `null` to hide the icons.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
items:
|
||||
- label: '1. What is NuxtLabs UI?'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: '2. Getting Started'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: '3. Theming'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: '4. Components'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
props:
|
||||
openIcon: 'i-heroicons-plus'
|
||||
closeIcon: 'i-heroicons-minus'
|
||||
excludedProps:
|
||||
- openIcon
|
||||
- closeIcon
|
||||
---
|
||||
::
|
||||
|
||||
### Multiple
|
||||
|
||||
Use the `multiple` prop to to allow multiple elements to be opened at the same time.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
items:
|
||||
- label: 'What is NuxtLabs UI?'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: 'Getting Started'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: 'Theming'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: 'Components'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
props:
|
||||
multiple: true
|
||||
excludedProps:
|
||||
- defaultOpen
|
||||
---
|
||||
::
|
||||
|
||||
### Open
|
||||
|
||||
Use the `default-open` prop to open all items by default. Works better when the `multiple` prop is set to `true`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
items:
|
||||
- label: 'What is NuxtLabs UI?'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: 'Getting Started'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: 'Theming'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
- label: 'Components'
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
props:
|
||||
defaultOpen: true
|
||||
multiple: true
|
||||
excludedProps:
|
||||
- defaultOpen
|
||||
- multiple
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
You can use slots to customize the buttons and items content of the Accordion.
|
||||
|
||||
### `default`
|
||||
|
||||
Use the `#default` slot to customize the trigger buttons. You will have access to the `item`, `index`, `open` properties and `close` method in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:accordion-example-default-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items" :ui="{ wrapper: 'flex flex-col w-full' }">
|
||||
<template #default="{ item, index, open }">
|
||||
<UButton color="gray" variant="ghost" class="border-b border-gray-200 dark:border-gray-700" :ui="{ rounded :'rounded-none', padding: { sm:'p-3' } }">
|
||||
<template #leading>
|
||||
<div class="w-6 h-6 rounded-full bg-primary-500 dark:bg-primary-400 flex items-center justify-center -my-1">
|
||||
<UIcon :name="item.icon" class="w-4 h-4 text-white dark:text-gray-900" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<span class="truncate">{{ index + 1 }}. {{ item.label }}</span>
|
||||
|
||||
<template #trailing>
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="w-5 h-5 ms-auto transform transition-transform duration-200"
|
||||
:class="[open && 'rotate-90']"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### `item`
|
||||
|
||||
Use the `#item` slot to customize the items content or pass a `slot` property to customize a specific item. You will have access to the `item`, `index`, `open` properties and `close` method in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:accordion-example-item-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
slot: 'getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
defaultOpen: true,
|
||||
slot: 'installation'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
defaultOpen: true,
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, ...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items">
|
||||
<template #item="{ item }">
|
||||
<p class="italic text-gray-900 dark:text-white text-center">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #getting-started>
|
||||
<div class="flex flex-col justify-center items-center gap-1">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
<span class="hidden sm:block">NuxtLabs</span><span class="sm:text-primary-500 dark:sm:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Fully styled and customizable components for Nuxt.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #installation="{ description }">
|
||||
<div class="flex flex-col justify-center items-center gap-1 mb-4">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
Installation
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Install <code>@nuxthq/ui</code> dependency to your project:
|
||||
</p>
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<code>$ npm install @nuxtlabs/ui</code>
|
||||
<code>$ nnpm install -D @nuxthq/ui</code>
|
||||
<code>$ pnpm i -D @nuxthq/ui</code>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
189
docs/content/2.elements/2.alert.md
Normal file
189
docs/content/2.elements/2.alert.md
Normal file
@@ -0,0 +1,189 @@
|
||||
---
|
||||
description: Display an alert element to draw attention.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Alert.vue
|
||||
navigation:
|
||||
badge: Edge
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Pass a `title` to your Alert.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
title: 'Heads up!'
|
||||
---
|
||||
::
|
||||
|
||||
### Description
|
||||
|
||||
You can add a `description` in addition of the `title`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
title: 'Heads up!'
|
||||
props:
|
||||
description: 'You can add components to your app using the cli.'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
title: 'Heads up!'
|
||||
props:
|
||||
icon: 'i-heroicons-command-line'
|
||||
description: 'You can add components to your app using the cli.'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Avatar
|
||||
|
||||
Use the [avatar](/elements/avatar) prop as an `object` and configure it with any of its props.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
title: 'Heads up!'
|
||||
props:
|
||||
description: 'You can add components to your app using the cli.'
|
||||
avatar:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
excludedProps:
|
||||
- avatar
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Alert.
|
||||
|
||||
- `color` can be any color from the `ui.colors` object or `white` (default).
|
||||
- `variant` can be `solid` (default), `outline`, `soft` or `subtle`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
title: 'Heads up!'
|
||||
description: 'You can add components to your app using the cli.'
|
||||
props:
|
||||
icon: 'i-heroicons-command-line'
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
extraColors:
|
||||
- white
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Close
|
||||
|
||||
Use the `close-button` prop to hide or customize the close button on the Alert.
|
||||
|
||||
You can pass all the props of the [Button](/elements/button) component to customize it through the `close-button` prop or globally through `ui.alert.default.closeButton`.
|
||||
|
||||
It defaults to `null` which means no close button will be displayed. A `close` event will be emitted when the close button is clicked.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
title: 'Heads up!'
|
||||
props:
|
||||
closeButton:
|
||||
icon: 'i-heroicons-x-mark-20-solid'
|
||||
color: 'gray'
|
||||
variant: 'link'
|
||||
padded: false
|
||||
excludedProps:
|
||||
- closeButton
|
||||
---
|
||||
::
|
||||
|
||||
### Actions
|
||||
|
||||
Use the `actions` prop to add actions to the Alert.
|
||||
|
||||
Like for `closeButton`, you can pass all the props of the [Button](/elements/button) component plus a `click` function in the action but also customize the default style for the actions globally through `ui.alert.default.actionButton`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
title: 'Heads up!'
|
||||
props:
|
||||
actions:
|
||||
- label: Action 1
|
||||
- variant: 'ghost'
|
||||
color: 'gray'
|
||||
label: Action 2
|
||||
excludedProps:
|
||||
- actions
|
||||
---
|
||||
::
|
||||
|
||||
Actions will render differently whether you have a `description` set.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
title: 'Heads up!'
|
||||
description: 'You can add components to your app using the cli.'
|
||||
props:
|
||||
actions:
|
||||
- variant: 'solid'
|
||||
color: 'primary'
|
||||
label: Action 1
|
||||
- variant: 'outline'
|
||||
color: 'primary'
|
||||
label: Action 2
|
||||
excludedProps:
|
||||
- actions
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `title` / `description`
|
||||
|
||||
Use the `#title` and `#description` slots to customize the Alert.
|
||||
|
||||
This can be handy when you want to display HTML content. To achieve this, you can define those slots and use the `v-html` directive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:alert-example-html
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UAlert title="Heads <i>up</i>!" icon="i-heroicons-command-line">
|
||||
<template #title="{ title }">
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
You can add <b>components</b> to your app using the <u>cli</u>.
|
||||
</template>
|
||||
</UAlert>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a short text to represent a status or a category.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the default slot to set the text of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
You can also use the `label` prop:
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: Badge
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
github: true
|
||||
description: Display an image that represents a resource or a group of resources.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Avatar.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -29,7 +32,7 @@ baseProps:
|
||||
|
||||
### Chip
|
||||
|
||||
Use the `chip-color`, `chip-text` :u-badge{label="Edge" class="!rounded-full"} and `chip-position` props to display a chip on the Avatar.
|
||||
Use the `chip-color`, `chip-text` and `chip-position` props to display a chip on the Avatar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
139
docs/content/2.elements/4.badge.md
Normal file
139
docs/content/2.elements/4.badge.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
description: Display a short text to represent a status or a category.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Badge.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the default slot to set the text of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
You can also use the `label` prop:
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: Badge
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Badge.
|
||||
|
||||
- `variant` can be `solid` (default), `outline`, `soft` or `subtle`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
Besides all the colors from the `ui.colors` object, you can also use the `white` and `black` colors with their pre-defined variants.
|
||||
|
||||
#### White :u-badge{label="Edge" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
#### Gray :u-badge{label="Edge" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
#### Black :u-badge{label="Edge" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'black'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
link: 1
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
### Rounded
|
||||
|
||||
To customize the border radius of the Badge, you can use the `ui` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
ui:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- ui
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
You can customize the whole [preset](#preset) by using the `ui` prop.
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
github: true
|
||||
description: Create a button with icon or link capabilities.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Button.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -109,6 +112,26 @@ props:
|
||||
Button
|
||||
::
|
||||
|
||||
### Rounded
|
||||
|
||||
To customize the border radius of the Button, you can use the `ui` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
ui:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- ui
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
You can customize the whole [preset](#preset) by using the `ui` prop.
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
@@ -201,6 +224,8 @@ props:
|
||||
Button
|
||||
::
|
||||
|
||||
You can also pass any property from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `target`, `exact`, etc.
|
||||
|
||||
### Padded
|
||||
|
||||
Use the `padded` prop to remove the padding of the Button.
|
||||
@@ -1,9 +1,12 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a list of actions in a dropdown menu.
|
||||
headlessui:
|
||||
label: 'Menu'
|
||||
to: 'https://headlessui.com/vue/menu'
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Dropdown.vue
|
||||
- label: Menu
|
||||
icon: i-simple-icons-headlessui
|
||||
to: https://headlessui.com/vue/menu
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -12,12 +15,14 @@ Pass an array of arrays to the `items` prop of the Dropdown component. Each arra
|
||||
|
||||
- `label` - The label of the item.
|
||||
- `icon` - The icon of the item.
|
||||
- `iconClass` - The class of the icon of the item.
|
||||
- `avatar` - The avatar of the item. You can pass all the props of the [Avatar](/elements/avatar) component.
|
||||
- `shortcuts` - The shortcuts of the item.
|
||||
- `slot` - The slot of the item.
|
||||
- `disabled` - Whether the item is disabled.
|
||||
- `click` - The click handler of the item.
|
||||
|
||||
You can also pass properties from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `to`, `exact`, etc.
|
||||
You can also pass any property from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `to`, `exact`, etc.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
@@ -95,6 +100,68 @@ const items = [
|
||||
```
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `item`
|
||||
|
||||
Use the `#item` slot to customize the items content or pass a `slot` property to customize a specific item. You will have access to the `item` property in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'ben@example.com',
|
||||
slot: 'account',
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Settings',
|
||||
icon: 'i-heroicons-cog-8-tooth'
|
||||
}], [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open'
|
||||
}, {
|
||||
label: 'Changelog',
|
||||
icon: 'i-heroicons-megaphone'
|
||||
}, {
|
||||
label: 'Status',
|
||||
icon: 'i-heroicons-signal'
|
||||
}], [{
|
||||
label: 'Sign out',
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle'
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :ui="{ item: { disabled: 'cursor-text select-text' } }" :popper="{ placement: 'bottom-start' }">
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" />
|
||||
|
||||
<template #account="{ item }">
|
||||
<div class="text-left">
|
||||
<p>
|
||||
Signed in as
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
|
||||
</template>
|
||||
</UDropdown>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
github: true
|
||||
description: Display an icon from Iconify library.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Icon.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -12,7 +15,7 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-exclamation-triangle"}
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
When playing with the `name` prop above, you won't be able to use any icon you want as icons are bundled on build as explained in the [theming section](/getting-started/theming#icons).
|
||||
::
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
---
|
||||
github: true
|
||||
title: 'Keyboard Key'
|
||||
description: Display a keyboard key in a text block.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Kbd.vue
|
||||
navigation:
|
||||
title: 'Kbd'
|
||||
---
|
||||
24
docs/content/2.elements/9.link.md
Normal file
24
docs/content/2.elements/9.link.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 'Link'
|
||||
description: Render a NuxtLink but with superpowers.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Link.vue
|
||||
navigation:
|
||||
badge: Edge
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The Link component is a wrapper around [`<NuxtLink>`](https://nuxt.com/docs/api/components/nuxt-link) through the [`custom`](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-custom) prop that provides a few extra props:
|
||||
|
||||
- `inactive-class` prop to set a class when the link is inactive, `active-class` is used when active.
|
||||
- `exact` prop to style with `active-class` when the link is active and the route is exactly the same as the current route.
|
||||
- `exact-query` and `exact-hash` props to style with `active-class` when the link is active and the query or hash is exactly the same as the current query or hash.
|
||||
|
||||
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 is used underneath by the [Button](/elements/button), [Dropdown](/elements/dropdown) and [VerticalNavigation](/navigation/vertical-navigation) components.
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
github: true
|
||||
description: Display an input field.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/forms/Input.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -218,7 +221,7 @@ const q = ref('')
|
||||
```
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-exclamation-triangle-20-solid"}
|
||||
::callout{icon="i-heroicons-exclamation-triangle-20-solid"}
|
||||
As leading and trailing icons are wrapped around a `pointer-events-none` class, if you inject a clickable element in the slot, you need to remove this class to make it clickable by adding `:ui="{ icon: { trailing: { pointer: '' } } }"` to the Input.
|
||||
::
|
||||
|
||||
|
||||
321
docs/content/3.forms/10.form.md
Normal file
321
docs/content/3.forms/10.form.md
Normal file
@@ -0,0 +1,321 @@
|
||||
---
|
||||
description: Collect and validate form data.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/forms/Form.ts
|
||||
navigation:
|
||||
badge: Edge
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi) or your own validation logic. It works seamlessly with the FormGroup component to automatically display error messages around form elements.
|
||||
|
||||
The Form component requires the `validate` and `state` props for form validation.
|
||||
|
||||
- `state` - a reactive object that holds the current state of the form.
|
||||
- `validate` - a function that takes the form's state as input and returns an array of `FormError` objects with the following fields:
|
||||
- `message` - the error message to display.
|
||||
- `path` - the path to the form element matching the `name`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-basic{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { FormError } from '@nuxthq/ui/dist/runtime/types'
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const validate = (state: any): FormError[] => {
|
||||
const errors = []
|
||||
if (!state.email) errors.push({ path: 'email', message: 'Required' })
|
||||
if (!state.password) errors.push({ path: 'password', message: 'Required' })
|
||||
return errors
|
||||
}
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:validate="validate"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Schema
|
||||
|
||||
You can provide a schema from [Yup](#yup), [Zod](#zod) or [Joi](#joi) through the `schema` prop to validate the state. It's important to note that **none of these libraries are included** by default, so make sure to **install the one you need**.
|
||||
|
||||
### Yup
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-yup{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { object, string, InferType } from 'yup'
|
||||
|
||||
const schema = object({
|
||||
email: string().email('Invalid email').required('Required'),
|
||||
password: string()
|
||||
.min(8, 'Must be at least 8 characters')
|
||||
.required('Required')
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Zod
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-zod{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
password: z.string().min(8, 'Must be at least 8 characters')
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Joi
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-joi{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import Joi from 'joi'
|
||||
import type { Schema } from 'joi'
|
||||
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string()
|
||||
.min(8)
|
||||
.required()
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit () {
|
||||
await form.value!.validate()
|
||||
// Do something with state.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Type Inference
|
||||
|
||||
You can utilize Zod and Yup's type inference feature to automatically infer the types of your schema and form data. This allows for strong type checking and better code validation, reducing the likelihood of errors.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { Form } from '@nuxthq/ui/dist/runtime/types'
|
||||
|
||||
// [...]
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
password: z.string().min(8, 'Must be at least 8 characters')
|
||||
})
|
||||
|
||||
const state: Partial<Schema> = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
// For Yup, use:
|
||||
// type Schema = InferType<typeof schema>
|
||||
const form = ref<Form<Schema>>()
|
||||
|
||||
async function submit() {
|
||||
const data: Schema = await form.value!.validate()
|
||||
// Do something with data
|
||||
}
|
||||
</script>
|
||||
// [...]
|
||||
```
|
||||
|
||||
## Other libraries
|
||||
|
||||
For other validation libraries, you can define your own component with custom validation logic.
|
||||
|
||||
Here is an example with [Vuelidate](https://github.com/vuelidate/vuelidate):
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import useVuelidate from '@vuelidate/core';
|
||||
|
||||
const props = defineProps({
|
||||
rules: { type: Object, required: true },
|
||||
model: { type: Object, required: true },
|
||||
});
|
||||
|
||||
const form = ref();
|
||||
const v = useVuelidate(props.rules, props.model);
|
||||
|
||||
async function validateWithVuelidate() {
|
||||
v.value.$touch();
|
||||
await v.value.$validate();
|
||||
return v.value.$errors.map((error) => ({
|
||||
message: error.$message,
|
||||
path: error.$propertyPath,
|
||||
}));
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate: async () => {
|
||||
await form.value.validate();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :model="model" :validate="validateWithVuelidate">
|
||||
<slot />
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Input events
|
||||
|
||||
The Form component automatically triggers validation upon input `blur` or `change` events. This ensures that any errors are displayed as soon as the user interacts with the form elements.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-elements{class="space-y-4 w-60"}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a textarea field.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/forms/Textarea.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user