mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e34c513b1a | ||
|
|
06d4510d1c | ||
|
|
819b5f8a17 | ||
|
|
15a40f53f2 | ||
|
|
dd55b4f602 | ||
|
|
ec58948153 | ||
|
|
27db7fdd95 | ||
|
|
2fc73ffafe | ||
|
|
5908d1a96b | ||
|
|
d02858f6c4 | ||
|
|
10ec3d533e | ||
|
|
d39e2de935 | ||
|
|
9df9b9a2df | ||
|
|
37fdf224c0 | ||
|
|
73d0fa7273 | ||
|
|
11ccbbb24e | ||
|
|
ceb2ed36d4 | ||
|
|
83c3be716a | ||
|
|
ca62ce13d3 | ||
|
|
16663887ca | ||
|
|
762c5ebba1 | ||
|
|
f4be95dcf5 | ||
|
|
c9b9bd6fb9 | ||
|
|
55697e601e | ||
|
|
0cb5cc3947 | ||
|
|
f245b4677c | ||
|
|
a1b38c4b66 | ||
|
|
d4f1b5ef82 | ||
|
|
6cc77a3e6c | ||
|
|
7339324355 | ||
|
|
bcc46b87f5 | ||
|
|
b666c4a1a8 | ||
|
|
a35bfc7343 | ||
|
|
a97593985c | ||
|
|
e1e5fa902b | ||
|
|
9b976a0b68 | ||
|
|
9952581d5b | ||
|
|
60e195ae60 | ||
|
|
7c1052f3f7 | ||
|
|
60fa2beed0 | ||
|
|
050c9c8db9 | ||
|
|
9361d47cf7 | ||
|
|
b3caec3a7d | ||
|
|
4dbc6240c5 | ||
|
|
7cb987de42 | ||
|
|
91511b921d | ||
|
|
f323379909 | ||
|
|
ded6a7f73d | ||
|
|
9a1a1b8caf | ||
|
|
de38afd97b | ||
|
|
a3046aa256 | ||
|
|
3c71bf36b0 | ||
|
|
c55871b844 | ||
|
|
0eb8d8f7ec | ||
|
|
68f6956d6e | ||
|
|
6f0bfb5d89 | ||
|
|
4a62dac8a4 | ||
|
|
042603a3c7 | ||
|
|
c601fc6c55 | ||
|
|
196e9ac7d4 | ||
|
|
bfe418f614 | ||
|
|
3d205d0c8a | ||
|
|
f3c491a417 | ||
|
|
d7cbd05533 | ||
|
|
8451f4d9bb | ||
|
|
c153138db1 | ||
|
|
c73a2ab676 | ||
|
|
e81d5cf998 | ||
|
|
7891ccb9ac | ||
|
|
c84438f491 | ||
|
|
844b3185e9 | ||
|
|
ed4b5e0077 | ||
|
|
a2d70f04e9 | ||
|
|
1f9a063d98 | ||
|
|
033910d90a | ||
|
|
3e783277be | ||
|
|
3ff4f0f8f5 | ||
|
|
c37ad8b79a | ||
|
|
f5f33882f9 | ||
|
|
5a2644b329 | ||
|
|
327c7769da | ||
|
|
a0ffd3e334 | ||
|
|
18e8d28272 | ||
|
|
339eaf69a5 | ||
|
|
53b26b8194 | ||
|
|
abbcc37fbb | ||
|
|
5296cf2319 | ||
|
|
3cb3914386 | ||
|
|
5ce60f775d | ||
|
|
8138814d71 | ||
|
|
56a19830b0 | ||
|
|
2c5559b73e | ||
|
|
f5f76cc77e | ||
|
|
4005defb39 | ||
|
|
23d5dc7b98 | ||
|
|
f785ecd46f | ||
|
|
4ce23746da | ||
|
|
8ba2a791e4 | ||
|
|
68f024f742 | ||
|
|
c5ce997ba9 | ||
|
|
9c05b3a317 | ||
|
|
a5d1661d66 | ||
|
|
0e116e6276 | ||
|
|
d112808994 | ||
|
|
f69024243e | ||
|
|
9023227cc0 | ||
|
|
ace8fc1c00 | ||
|
|
7be2af7127 | ||
|
|
2b7c5c575f | ||
|
|
4a18ff1da9 | ||
|
|
51f4d54999 | ||
|
|
cca9dfbe22 | ||
|
|
afbd47b7b0 | ||
|
|
a735483381 | ||
|
|
b4f7b035f7 | ||
|
|
0c36996adb | ||
|
|
40f3b16100 | ||
|
|
a8279d1c97 | ||
|
|
360cfe663f | ||
|
|
e5cbeac34b | ||
|
|
431a61c2b5 | ||
|
|
8867936e01 | ||
|
|
9f4d88e0aa | ||
|
|
8bfd3591a6 | ||
|
|
550ac10e49 | ||
|
|
6137acad04 | ||
|
|
0bd12fdf92 | ||
|
|
3ae78aadee | ||
|
|
7a48e8c45d | ||
|
|
ddbb431953 | ||
|
|
3697dbeda2 | ||
|
|
92b86186e7 | ||
|
|
827f2f45d9 | ||
|
|
96296c3d38 | ||
|
|
94cabca65a | ||
|
|
e16379fdbd | ||
|
|
1df07e2b4c | ||
|
|
972618fac8 | ||
|
|
168ef018f1 | ||
|
|
e4d500f7c7 | ||
|
|
aa42c4a5d1 | ||
|
|
fe348b48c6 | ||
|
|
cf93d968af | ||
|
|
3b8e014449 | ||
|
|
f4a3479e7c | ||
|
|
ccb353d4bd | ||
|
|
3c5c3389f8 | ||
|
|
eb9ce6a0dd | ||
|
|
0c807db005 | ||
|
|
49a753f80f | ||
|
|
498db5ca21 | ||
|
|
25ab781c14 | ||
|
|
af3db2a544 | ||
|
|
537bd08aaa | ||
|
|
f3c7ad8470 |
23
.github/workflows/ci-dev.yml
vendored
23
.github/workflows/ci-dev.yml
vendored
@@ -12,13 +12,20 @@ jobs:
|
||||
ci:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [18]
|
||||
|
||||
env:
|
||||
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
@@ -46,6 +53,15 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
src:
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
@@ -61,8 +77,11 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Test
|
||||
run: pnpm run test run
|
||||
|
||||
- name: Release Edge
|
||||
if: github.event_name == 'push'
|
||||
if: github.event_name == 'push' && steps.changes.outputs.src == 'true'
|
||||
run: ./scripts/release-edge.sh
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
|
||||
|
||||
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -4,9 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
@@ -17,8 +14,11 @@ jobs:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [18]
|
||||
|
||||
env:
|
||||
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
@@ -61,6 +61,9 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Test
|
||||
run: pnpm run test run
|
||||
|
||||
- name: Version Check
|
||||
id: check
|
||||
uses: EndBug/version-check@v2
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
},
|
||||
"github": {
|
||||
"release": true,
|
||||
"releaseName": "v${version}",
|
||||
"web": true
|
||||
},
|
||||
"hooks": {
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"prettier.enable": false
|
||||
}
|
||||
79
CHANGELOG.md
79
CHANGELOG.md
@@ -1,5 +1,82 @@
|
||||
# Changelog
|
||||
|
||||
## [2.11.0](https://github.com/nuxt/ui/compare/v2.10.0...v2.11.0) (2023-11-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Breadcrumb:** new component ([#506](https://github.com/nuxt/ui/issues/506)) ([a35bfc7](https://github.com/nuxt/ui/commit/a35bfc734372cd24a8f86fca7b69f091051ce918))
|
||||
* **Checkbox:** config `label`, `required` and `help` size ([a1b38c4](https://github.com/nuxt/ui/commit/a1b38c4b66a16fefe8b514ec699a84309fcb7225))
|
||||
* **Chip:** new component ([#886](https://github.com/nuxt/ui/issues/886)) ([d4f1b5e](https://github.com/nuxt/ui/commit/d4f1b5ef82f58c2df4dd9491ceb61b55da7ba4c3))
|
||||
* **FormGroup:** add eager validation ([#992](https://github.com/nuxt/ui/issues/992)) ([d39e2de](https://github.com/nuxt/ui/commit/d39e2de935bcbbaf86c4b4b368e81bb08859b2e6))
|
||||
* **Icon:** switch to `nuxt-icon` with `dynamic` prop or app config ([#862](https://github.com/nuxt/ui/issues/862)) ([c601fc6](https://github.com/nuxt/ui/commit/c601fc6c5583763a2cdf6c575dda55c46311612a))
|
||||
* **module:** allow options override of `@egoist/tailwindcss-icons` plugin ([#1013](https://github.com/nuxt/ui/issues/1013)) ([ec58948](https://github.com/nuxt/ui/commit/ec58948153eb9c3048c41187ae505072a817b746))
|
||||
* **Notification:** customize default timeout ([#1003](https://github.com/nuxt/ui/issues/1003)) ([83c3be7](https://github.com/nuxt/ui/commit/83c3be716aa42eee70a1bbc3b8a28b7fa483c9bf))
|
||||
* **Popover:** ability to add overlay ([#1014](https://github.com/nuxt/ui/issues/1014)) ([06d4510](https://github.com/nuxt/ui/commit/06d4510d1c485ede49d1572454aeb8581384626e))
|
||||
* **SelectMenu:** allows to clear search query on close ([#968](https://github.com/nuxt/ui/issues/968)) ([11ccbbb](https://github.com/nuxt/ui/commit/11ccbbb24ef61e6bd3bb703f950955dd21d6a3eb))
|
||||
* **Textarea:** add default slot for complex usages ([55697e6](https://github.com/nuxt/ui/commit/55697e601e9b94e2159aa27613edd7265d5d06af)), closes [#971](https://github.com/nuxt/ui/issues/971)
|
||||
* **Toggle:** add `size` prop ([#950](https://github.com/nuxt/ui/issues/950)) ([3c71bf3](https://github.com/nuxt/ui/commit/3c71bf36b0232745765c6860af2be7f44bf948a0))
|
||||
* **types:** support custom values from `app.config.ts` ([#863](https://github.com/nuxt/ui/issues/863)) ([7339324](https://github.com/nuxt/ui/commit/7339324355362eebd30707fdd1944270e41525f4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Alert:** improve config options ([91511b9](https://github.com/nuxt/ui/commit/91511b921d65150f8da9c71e361d305477234f84)), closes [#760](https://github.com/nuxt/ui/issues/760)
|
||||
* **Alert:** prevent `gap` when no close button or action ([9a1a1b8](https://github.com/nuxt/ui/commit/9a1a1b8caf1c040c458230458b9fa9cbfb32a1bb)), closes [#831](https://github.com/nuxt/ui/issues/831)
|
||||
* **ButtonGroup:** handle components with children ([#999](https://github.com/nuxt/ui/issues/999)) ([f4be95d](https://github.com/nuxt/ui/commit/f4be95dcf5a07c964ae9f2555070d437e0388c13))
|
||||
* **CommandPalette:** activate first option after async search ([#981](https://github.com/nuxt/ui/issues/981)) ([a975939](https://github.com/nuxt/ui/commit/a97593985c93d4cc30ceff0e3bdf8070f17b63f6))
|
||||
* **defineShortcuts:** support minus `-` key ([#962](https://github.com/nuxt/ui/issues/962)) ([de38afd](https://github.com/nuxt/ui/commit/de38afd97b7bfd9af2619a17a42f27177abfec7e))
|
||||
* **Dropdown:** pass event to `click` function ([60fa2be](https://github.com/nuxt/ui/commit/60fa2beed0ef0dbac6429033cc96803edf847120))
|
||||
* **Dropdown:** use `NuxtLink` with `custom` prop to close on select ([f323379](https://github.com/nuxt/ui/commit/f3233799096b18b1d6c86391799a7c98a110fa4d)), closes [#899](https://github.com/nuxt/ui/issues/899)
|
||||
* **FormGroup:** hydration mismatch on inputId ([#942](https://github.com/nuxt/ui/issues/942)) ([a3046aa](https://github.com/nuxt/ui/commit/a3046aa25626ca50e9d9fc6288321940445e88a1))
|
||||
* **FormGroup:** remove inputId if the input is a fieldset ([#914](https://github.com/nuxt/ui/issues/914)) ([e81d5cf](https://github.com/nuxt/ui/commit/e81d5cf99831cfc320049051eeaf36f15951282b))
|
||||
* **Input/Textarea:** add `v-model` modifiers ([#856](https://github.com/nuxt/ui/issues/856)) ([68f6956](https://github.com/nuxt/ui/commit/68f6956d6e0cb5155e19b8d464a42953b8e30475))
|
||||
* **Link:** handle `active` state when `to` prop is not provided ([6cc77a3](https://github.com/nuxt/ui/commit/6cc77a3e6cbb263b649de0ea044894e0b7c4258a)), closes [#988](https://github.com/nuxt/ui/issues/988)
|
||||
* **Link:** reactivity issue with `active` prop ([15a40f5](https://github.com/nuxt/ui/commit/15a40f53f218bbe768262efc03dd7eaaf147ed6e)), closes [nuxt/nuxt.com#1432](https://github.com/nuxt/nuxt.com/issues/1432)
|
||||
* **module:** `boolean` types and bump nuxt to `3.8.2` ([#1006](https://github.com/nuxt/ui/issues/1006)) ([ca62ce1](https://github.com/nuxt/ui/commit/ca62ce13d3238819475528de0340416e6db9e5e6))
|
||||
* **module:** use correct alias for `[#ui](https://github.com/nuxt/ui/issues/ui)-colors` ([#913](https://github.com/nuxt/ui/issues/913)) ([c84438f](https://github.com/nuxt/ui/commit/c84438f491e7e3f8af5c6d892a2141b9ada2c155))
|
||||
* **Notification:** improve config options ([7cb987d](https://github.com/nuxt/ui/commit/7cb987de42ad89efc227eef47a8e06e7bc93206f))
|
||||
* **Notification:** prevent `gap` when no close button or action ([ded6a7f](https://github.com/nuxt/ui/commit/ded6a7f73d9ea57b5e771ce192c9ee36e6f98bba))
|
||||
* **Notifications:** teleport to `body` ([#909](https://github.com/nuxt/ui/issues/909)) ([8451f4d](https://github.com/nuxt/ui/commit/8451f4d9bbe51972688966f529cf0713060adb7a))
|
||||
* **Progress:** percentage calculation ([#939](https://github.com/nuxt/ui/issues/939)) ([c55871b](https://github.com/nuxt/ui/commit/c55871b8449e9947e84ecb2f9667eea287b579e6))
|
||||
* **Radio:** prevent `help` text from inlining with label ([#894](https://github.com/nuxt/ui/issues/894)) ([a2d70f0](https://github.com/nuxt/ui/commit/a2d70f04e98ce181ac217eaf6b66a8728af95805))
|
||||
* **SelectMenu:** fixes non-strings and nested searchable attributes ([#967](https://github.com/nuxt/ui/issues/967)) ([37fdf22](https://github.com/nuxt/ui/commit/37fdf224c07e47312c731b20080533ad7d8d786c))
|
||||
|
||||
## [2.10.0](https://github.com/nuxt/ui/compare/v2.9.0...v2.10.0) (2023-10-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **CommandPalette:** handle `filter` attribute in groups ([#871](https://github.com/nuxt/ui/issues/871)) ([8ba2a79](https://github.com/nuxt/ui/commit/8ba2a791e4877682705bd752d4ab6f9c52d0b37b))
|
||||
* **Divider:** new component ([#757](https://github.com/nuxt/ui/issues/757)) ([eb9ce6a](https://github.com/nuxt/ui/commit/eb9ce6a0ddb7d73e3d3accee000ac71c20b96d1b))
|
||||
* **Form:** handle `[@error](https://github.com/error)` event ([#718](https://github.com/nuxt/ui/issues/718)) ([e16379f](https://github.com/nuxt/ui/commit/e16379fdbdff6c98e96dc03cc67f3912f2f61075))
|
||||
* **Input/Textarea:** allow specifying autofocus delay for page transitions ([#816](https://github.com/nuxt/ui/issues/816)) ([8bfd359](https://github.com/nuxt/ui/commit/8bfd3591a624ad7b77bcd9d3c38961a1ba59f23c))
|
||||
* **Meter:** new component ([#827](https://github.com/nuxt/ui/issues/827)) ([abbcc37](https://github.com/nuxt/ui/commit/abbcc37fbb4b52b1503a69f8312cbecfe222f675))
|
||||
* **Pagination:** add first and last page buttons ([#842](https://github.com/nuxt/ui/issues/842)) ([c5ce997](https://github.com/nuxt/ui/commit/c5ce997ba9d7abdb8282fcd34b88c380a7a4c592))
|
||||
* **Popover:** manual mode & horizontal offset ([#781](https://github.com/nuxt/ui/issues/781)) ([92b8618](https://github.com/nuxt/ui/commit/92b86186e7b8a987eec1da9cf45a0ec378d421cf))
|
||||
* **popper:** `arrow` option & docs consistency across components ([#875](https://github.com/nuxt/ui/issues/875)) ([f785ecd](https://github.com/nuxt/ui/commit/f785ecd46fdff77ecb8579d8a7edc463bcce2407))
|
||||
* **Progress:** new component ([#697](https://github.com/nuxt/ui/issues/697)) ([2c5559b](https://github.com/nuxt/ui/commit/2c5559b73ea22f1021c18c2561de1e6cd6f9741f))
|
||||
* **RadioGroup:** configurable label size ([#881](https://github.com/nuxt/ui/issues/881)) ([5a2644b](https://github.com/nuxt/ui/commit/5a2644b329dd1bb85a8ca70f849e108dbb93c776))
|
||||
* **RadioGroup:** new component ([#730](https://github.com/nuxt/ui/issues/730)) ([23d5dc7](https://github.com/nuxt/ui/commit/23d5dc7b981d56127dd2bd3f03d752a76f36653c))
|
||||
* **Range:** add `2xs`, `xs`, `xl` and `2xl` sizes to match progress component ([3cb3914](https://github.com/nuxt/ui/commit/3cb3914386e465180337ff8bf3f78e07a14bbafb)), closes [#673](https://github.com/nuxt/ui/issues/673)
|
||||
* **Table:** add `v-model:sort` prop ([#803](https://github.com/nuxt/ui/issues/803)) ([9f4d88e](https://github.com/nuxt/ui/commit/9f4d88e0aa7ec8cbbdae3fccd372d8c5e81d7ad0))
|
||||
* **Tooltip:** adding option to show popper arrow ([#868](https://github.com/nuxt/ui/issues/868)) ([4ce2374](https://github.com/nuxt/ui/commit/4ce23746da27ad0ef9b1833e41105165045f1cb8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Accordion:** toggle correct element when keyboard press ([#805](https://github.com/nuxt/ui/issues/805)) ([96296c3](https://github.com/nuxt/ui/commit/96296c3d388a4f65f08e4a062f720d37f2c84ebc))
|
||||
* **Divider:** display a single border when no content ([3c5c338](https://github.com/nuxt/ui/commit/3c5c3389f8cdfcf9b70f1bb7d5553d0be55278a4))
|
||||
* **Dropdown:** use `NuxtLink` instead of `ULink` ([#882](https://github.com/nuxt/ui/issues/882)) ([c37ad8b](https://github.com/nuxt/ui/commit/c37ad8b79a61ffccbb8959ca07fdf54923313e00))
|
||||
* **FormGroup:** ensure size exists in config ([#695](https://github.com/nuxt/ui/issues/695)) ([f5f3388](https://github.com/nuxt/ui/commit/f5f33882f9ad48944e54f31cb784bedf26dccbd1))
|
||||
* **Modal:** remove padding on mobile with `fullscreen` enabled ([550ac10](https://github.com/nuxt/ui/commit/550ac10e49d15e0b5435e031ec61f7defdaee445)), closes [#811](https://github.com/nuxt/ui/issues/811)
|
||||
* **Notification:** add roles for accessibility ([#724](https://github.com/nuxt/ui/issues/724)) ([40f3b16](https://github.com/nuxt/ui/commit/40f3b161003f71ecacf57b9641de66acd14e3fab))
|
||||
* **Table:** enable sorting for nested column keys ([#835](https://github.com/nuxt/ui/issues/835)) ([b4f7b03](https://github.com/nuxt/ui/commit/b4f7b035f7e802427e57fc7359020648a23eb71e))
|
||||
* **Table:** prevent `[@select](https://github.com/select)` event call when selecting all rows ([#838](https://github.com/nuxt/ui/issues/838)) ([51f4d54](https://github.com/nuxt/ui/commit/51f4d549998c0d570adc843e1f3835cbd163ae69))
|
||||
* **Tabs:** truncate buttons content ([ddbb431](https://github.com/nuxt/ui/commit/ddbb4319539e9e306ed9fc6f4f2145f20f13683a)), closes [#796](https://github.com/nuxt/ui/issues/796)
|
||||
* **types:** handle sub-objects in `app.config.ts` (button colors) ([7be2af7](https://github.com/nuxt/ui/commit/7be2af7127acba2e1228b7994ecd8eb40e5c376b)), closes [#858](https://github.com/nuxt/ui/issues/858)
|
||||
* use explicit type imports ([#830](https://github.com/nuxt/ui/issues/830)) ([a8279d1](https://github.com/nuxt/ui/commit/a8279d1c97c2f2c6a5d9fd971abb27767b5beb4c))
|
||||
|
||||
## [2.9.0](https://github.com/nuxt/ui/compare/v2.8.1...v2.9.0) (2023-10-02)
|
||||
|
||||
|
||||
@@ -819,4 +896,4 @@
|
||||
* **Toggle:** add missing `computed` import ([0f09c9b](https://github.com/nuxtlabs/ui/commit/0f09c9baae501458af029f853c78b1c10a3ac133))
|
||||
* **Tooltip:** missing `ref` import ([b08a8cc](https://github.com/nuxtlabs/ui/commit/b08a8cc0ac79e89817e338281a81c477d5ec645a))
|
||||
* **useTimer:** remove log ([c6dcbd1](https://github.com/nuxtlabs/ui/commit/c6dcbd1b2b542dab1850504a60451a485e2d4004))
|
||||
* **VerticalNavigation:** add `v-if` on label ([79d8e08](https://github.com/nuxtlabs/ui/commit/79d8e086f0c61887c52da6fe4a13f1bdf7077227))
|
||||
* **VerticalNavigation:** add `v-if` on label ([79d8e08](https://github.com/nuxtlabs/ui/commit/79d8e086f0c61887c52da6fe4a13f1bdf7077227))
|
||||
|
||||
11
README.md
11
README.md
@@ -71,6 +71,17 @@ Visit https://ui.nuxt.com to explore the documentation.
|
||||
- [vueuse/vueuse](https://github.com/vueuse/vueuse)
|
||||
- [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons)
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to Nuxt UI. Here are a few ways you can get involved:
|
||||
|
||||
- Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report.
|
||||
- Suggestions: Have any thoughts to enhance Nuxt UI? We'd love to hear them! Check out the [contribution guide](https://ui.nuxt.com/getting-started/contributing) to share your suggestions.
|
||||
|
||||
## Local Development
|
||||
|
||||
Follow the docs to [Set up your local development environment](https://ui.nuxt.com/getting-started/contributing#_2-local-development-setup) and contribute.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/LICENSE.md).
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# To link Nuxt UI Pro in development
|
||||
# Specify the path of @nuxt/ui-pro locally
|
||||
NUXT_UI_PRO_PATH=
|
||||
# To use Nuxt UI Pro in production
|
||||
NUXT_UI_PRO_TOKEN=
|
||||
# Production token for @nuxt/ui-pro, purchase on https://ui.nuxt.com/pro/purchase
|
||||
NUXT_UI_PRO_LICENSE=
|
||||
# Used when pre-rendering the docs for dynamic OG images
|
||||
NUXT_PUBLIC_SITE_URL=
|
||||
# Used to fetch `nuxt/ui-pro` docs content
|
||||
NUXT_GITHUB_TOKEN=
|
||||
|
||||
64
docs/app.vue
64
docs/app.vue
@@ -1,26 +1,22 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div>
|
||||
<Header />
|
||||
<Header v-if="!$route.path.startsWith('/examples')" :links="links" />
|
||||
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
|
||||
<Footer />
|
||||
<Footer v-if="!$route.path.startsWith('/examples')" />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUDocsSearch ref="searchRef" :files="files" :navigation="navigation" :groups="groups" />
|
||||
<LazyUDocsSearch ref="searchRef" :files="files" :navigation="navigation" :groups="groups" :links="links" />
|
||||
</ClientOnly>
|
||||
|
||||
<UNotifications>
|
||||
<template #title="{ title }">
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
|
||||
<template #description="{ description }">
|
||||
<span v-html="description" />
|
||||
</template>
|
||||
</UNotifications>
|
||||
</div>
|
||||
</template>
|
||||
@@ -42,10 +38,17 @@ const { data: files } = useLazyFetch<ParsedContent[]>('/api/search.json', { defa
|
||||
// Computed
|
||||
|
||||
const navigation = computed(() => {
|
||||
const main = nav.value.filter(item => item._path !== '/dev')
|
||||
const dev = nav.value.find(item => item._path === '/dev')?.children
|
||||
if (branch.value?.name === 'dev') {
|
||||
const dev = nav.value.find(item => item._path === '/dev')?.children
|
||||
const pro = nav.value.find(item => item._path === '/pro')
|
||||
|
||||
return branch.value?.name === 'dev' ? dev : main
|
||||
return [
|
||||
...dev,
|
||||
...(pro ? [pro] : [])
|
||||
]
|
||||
}
|
||||
|
||||
return nav.value.filter(item => item._path !== '/dev')
|
||||
})
|
||||
|
||||
const groups = computed(() => {
|
||||
@@ -58,6 +61,47 @@ const groups = computed(() => {
|
||||
|
||||
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
|
||||
|
||||
const links = computed(() => {
|
||||
return [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open',
|
||||
to: `${branch.value?.name === 'dev' ? '/dev' : ''}/getting-started`
|
||||
}, {
|
||||
label: 'Playground',
|
||||
icon: 'i-simple-icons-stackblitz',
|
||||
to: '/playground'
|
||||
}, {
|
||||
label: 'Roadmap',
|
||||
icon: 'i-heroicons-academic-cap',
|
||||
to: '/roadmap'
|
||||
}, !!navigation.value.find(item => item._path === '/pro') && {
|
||||
label: 'Pro',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
to: '/pro',
|
||||
children: [{
|
||||
label: 'Features',
|
||||
to: '/pro',
|
||||
exact: true,
|
||||
icon: 'i-heroicons-beaker',
|
||||
description: 'Discover all the features of Nuxt UI Pro.'
|
||||
}, {
|
||||
label: 'Guide',
|
||||
to: '/pro/guide',
|
||||
icon: 'i-heroicons-book-open',
|
||||
description: 'Learn how to use Nuxt UI Pro in your app.'
|
||||
}, {
|
||||
label: 'Components',
|
||||
to: '/pro/components',
|
||||
icon: 'i-heroicons-cube-transparent',
|
||||
description: 'Discover all the components available in Nuxt UI Pro.'
|
||||
}]
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-heroicons-rocket-launch',
|
||||
to: '/releases'
|
||||
}].filter(Boolean)
|
||||
})
|
||||
|
||||
// Watch
|
||||
|
||||
watch(() => searchRef.value?.commandPaletteRef?.query, debounce((query: string) => {
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
<template>
|
||||
<div v-if="$route.path !== '/playground'" class="w-full h-px bg-gray-200 dark:bg-gray-800 flex items-center justify-center">
|
||||
<div class="bg-white dark:bg-gray-900 px-4">
|
||||
<div class="w-full h-px bg-gray-200 dark:bg-gray-800 flex items-center justify-center">
|
||||
<div v-if="!['/playground', '/roadmap'].includes($route.path)" class="bg-white dark:bg-gray-900 px-4">
|
||||
<LogoOnly class="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UFooter :links="[]" :ui="{ bottom: { container: 'lg:py-4' } }">
|
||||
<UFooter>
|
||||
<template #left>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs" class="inline-block">
|
||||
<LogoLabs class="text-gray-900 dark:text-white h-4 w-auto" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #center>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<a v-if="$route.path.startsWith('/pro')" class="text-sm text-gray-500 dark:text-gray-400 hover:underline" href="https://ui.nuxt.com/pro/purchase" target="_blank">
|
||||
Purchase Nuxt UI Pro
|
||||
</a>
|
||||
<span v-else class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Published under <NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="text-gray-900 dark:text-white">
|
||||
MIT License
|
||||
</NuxtLink>
|
||||
|
||||
@@ -26,44 +26,40 @@
|
||||
target="_blank"
|
||||
icon="i-simple-icons-github"
|
||||
aria-label="GitHub"
|
||||
class="hidden lg:inline-flex"
|
||||
v-bind="($ui.button.secondary as any)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<BranchSelect />
|
||||
<UAsideLinks :links="links" />
|
||||
|
||||
<UNavigationTree :links="mapContentNavigation(navigation)" />
|
||||
<UDivider type="dashed" class="mt-4 mb-3" />
|
||||
|
||||
<BranchSelect v-if="!route.path.startsWith('/pro')" />
|
||||
|
||||
<UNavigationTree :links="mapContentNavigation(navigation)" :multiple="false" default-open />
|
||||
</template>
|
||||
</UHeader>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
import type { Link } from '#ui-pro/types'
|
||||
|
||||
defineProps<{
|
||||
links: Link[]
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
|
||||
const navigation = inject<Ref<NavItem[]>>('navigation')
|
||||
const nav = inject<Ref<NavItem[]>>('navigation')
|
||||
|
||||
const links = computed(() => {
|
||||
return [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open-solid',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Examples',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
to: '/getting-started/examples'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
icon: 'i-simple-icons-stackblitz',
|
||||
to: '/playground'
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-heroicons-rocket-launch-solid',
|
||||
to: 'https://github.com/nuxt/ui/releases',
|
||||
target: '_blank'
|
||||
}]
|
||||
const navigation = computed(() => {
|
||||
if (route.path.startsWith('/pro')) {
|
||||
return nav.value.find(item => item._path === '/pro')?.children
|
||||
}
|
||||
|
||||
return nav.value.filter(item => !item._path.startsWith('/pro'))
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<svg width="1020" height="200" viewBox="0 0 1020 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M377 200C379.16 200 381 198.209 381 196V103C381 103 386 112 395 127L434 194C435.785 197.74 439.744 200 443 200H470V50H443C441.202 50 439 51.4941 439 54V148L421 116L385 55C383.248 51.8912 379.479 50 376 50H350V200H377Z" fill="currentColor" />
|
||||
<path d="M726 92H739C742.314 92 745 89.3137 745 86V60H773V92H800V116H773V159C773 169.5 778.057 174 787 174H800V200H783C759.948 200 745 185.071 745 160V116H726V92Z" fill="currentColor" />
|
||||
<path d="M591 92V154C591 168.004 585.742 179.809 578 188C570.258 196.191 559.566 200 545 200C530.434 200 518.742 196.191 511 188C503.389 179.809 498 168.004 498 154V92H514C517.412 92 520.769 92.622 523 95C525.231 97.2459 526 98.5652 526 102V154C526 162.059 526.457 167.037 530 171C533.543 174.831 537.914 176 545 176C552.217 176 555.457 174.831 559 171C562.543 167.037 563 162.059 563 154V102C563 98.5652 563.769 96.378 566 94C567.96 91.9107 570.028 91.9599 573 92C573.411 92.0055 574.586 92 575 92H591Z" fill="currentColor" />
|
||||
<path d="M676 144L710 92H684C680.723 92 677.812 93.1758 676 96L660 120L645 97C643.188 94.1758 639.277 92 636 92H611L645 143L608 200H634C637.25 200 640.182 196.787 642 194L660 167L679 195C680.818 197.787 683.75 200 687 200H713L676 144Z" fill="currentColor" />
|
||||
<path d="M168 200H279C282.542 200 285.932 198.756 289 197C292.068 195.244 295.23 193.041 297 190C298.77 186.959 300.002 183.51 300 179.999C299.998 176.488 298.773 173.04 297 170.001L222 41C220.23 37.96 218.067 35.7552 215 34C211.933 32.2448 207.542 31 204 31C200.458 31 197.067 32.2448 194 34C190.933 35.7552 188.77 37.96 187 41L168 74L130 9.99764C128.228 6.95784 126.068 3.75491 123 2C119.932 0.245087 116.542 0 113 0C109.458 0 106.068 0.245087 103 2C99.9323 3.75491 96.7717 6.95784 95 9.99764L2 170.001C0.226979 173.04 0.00154312 176.488 1.90993e-06 179.999C-0.0015393 183.51 0.229648 186.959 2 190C3.77035 193.04 6.93245 195.244 10 197C13.0675 198.756 16.4578 200 20 200H90C117.737 200 137.925 187.558 152 164L186 105L204 74L259 168H186L168 200ZM89 168H40L113 42L150 105L125.491 147.725C116.144 163.01 105.488 168 89 168Z" fill="#00DC82" />
|
||||
<path d="M958 60.0001H938C933.524 60.0001 929.926 59.9395 927 63C924.074 65.8905 925 67.5792 925 72V141C925 151.372 923.648 156.899 919 162C914.352 166.931 908.468 169 899 169C889.705 169 882.648 166.931 878 162C873.352 156.899 873 151.372 873 141V72.0001C873 67.5793 872.926 65.8906 870 63.0001C867.074 59.9396 863.476 60.0001 859 60.0001H840V141C840 159.023 845.016 173.458 855 184C865.156 194.542 879.893 200 899 200C918.107 200 932.844 194.542 943 184C953.156 173.458 958 159.023 958 141V60.0001Z" fill="#00DC82" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1000 60.0233L1020 60V77L1020 128V156.007L1020 181L1020 189.004C1020 192.938 1019.98 194.429 1017 197.001C1014.02 199.725 1009.56 200 1005 200H986.001V181.006L986 130.012V70.0215C986 66.1576 986.016 64.5494 989 62.023C991.819 59.6358 995.437 60.0233 1000 60.0233Z" fill="#00DC82" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<svg width="1240" height="200" viewBox="0 0 1240 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M168 200H279C282.542 200 285.932 198.756 289 197C292.068 195.244 295.23 193.041 297 190C298.77 186.959 300.002 183.51 300 179.999C299.998 176.488 298.773 173.04 297 170.001L222 41C220.23 37.96 218.067 35.7552 215 34C211.933 32.2448 207.542 31 204 31C200.458 31 197.067 32.2448 194 34C190.933 35.7552 188.77 37.96 187 41L168 74L130 9.99764C128.228 6.95784 126.068 3.75491 123 2C119.932 0.245087 116.542 0 113 0C109.458 0 106.068 0.245087 103 2C99.9323 3.75491 96.7717 6.95784 95 9.99764L2 170.001C0.226979 173.04 0.00154312 176.488 1.90993e-06 179.999C-0.0015393 183.51 0.229648 186.959 2 190C3.77035 193.04 6.93245 195.244 10 197C13.0675 198.756 16.4578 200 20 200H90C117.737 200 137.925 187.558 152 164L186 105L204 74L259 168H186L168 200ZM89 168H40L113 42L150 105L125.491 147.725C116.144 163.01 105.488 168 89 168Z" fill="currentColor" />
|
||||
<path d="M375 200C377.16 200 379 198.209 379 196V103C379 103 384 112 393 127L432 194C433.785 197.74 437.744 200 441 200H468V50H441C439.202 50 437 51.4941 437 54V148L419 116L383 55C381.248 51.8912 377.479 50 374 50H348V200H375Z" fill="currentColor" />
|
||||
<path d="M724 92H737C740.314 92 743 89.3137 743 86V60H771V92H798V116H771V159C771 169.5 776.057 174 785 174H798V200H781C757.948 200 743 185.071 743 160V116H724V92Z" fill="currentColor" />
|
||||
<path d="M589 154V92H573L572.832 92.0002L572.498 92.001H572.497C571.979 92.0023 571.294 92.004 571 92L570.912 91.9988C567.987 91.9592 565.941 91.9315 564 94C561.769 96.378 561 98.5652 561 102V154C561 162.059 560.543 167.037 557 171C553.457 174.831 550.217 176 543 176C535.914 176 531.543 174.831 528 171C524.457 167.037 524 162.059 524 154V102C524 98.5652 523.231 97.2459 521 95C518.769 92.622 515.412 92 512 92H496V154C496 168.004 501.389 179.809 509 188C516.742 196.191 528.434 200 543 200C557.566 200 568.258 196.191 576 188C583.742 179.809 589 168.004 589 154Z" fill="currentColor" />
|
||||
<path d="M674 144L708 92H682C678.723 92 675.812 93.1758 674 96L658 120L643 97C641.188 94.1758 637.277 92 634 92H609L643 143L606 200H632C635.25 200 638.182 196.787 640 194L658 167L677 195C678.818 197.787 681.75 200 685 200H711L674 144Z" fill="currentColor" />
|
||||
<path d="M931 200V175H868V66C868 62.6863 865.314 60 862 60H838V194C838 197.314 840.686 200 844 200H931Z" fill="currentColor" />
|
||||
<path d="M1202 200C1225.14 200 1240 187.277 1240 169C1240 143.04 1220.69 140.838 1205.16 139.067C1194.72 137.877 1186 136.882 1186 129C1186 122.908 1190.35 120 1198 120C1205.45 120 1213.82 123.813 1215 132H1232.68C1236.12 132 1238.91 129.086 1238.06 125.757C1234.16 110.512 1218.99 101 1198 101C1177.8 101 1163 113.056 1163 130C1163 153.784 1181.4 156.618 1196.52 158.946C1207.06 160.569 1216 161.946 1216 170C1216 175.331 1209.43 179 1201 179C1190.8 179 1183.98 174.567 1183 166H1166.29C1162.87 166 1160.08 168.888 1160.81 172.233C1164.58 189.368 1180.39 200 1202 200Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1151 149C1151 179.068 1133.34 200 1104 200C1092.58 200 1081.51 195.469 1076 188V200H1049V60H1076V111C1081.51 103.914 1091.59 100 1104 100C1132.95 100 1151 118.932 1151 149ZM1075 150C1075 166.088 1084.23 177 1099 177C1113.37 177 1123 166.088 1123 150C1123 133.721 1113.37 122 1099 122C1084.23 122 1075 133.721 1075 150Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1005 105C998.647 101.954 991.004 101 983 101C974.487 101 967.353 101.954 961 105C954.647 108.046 949.558 112.669 946 118C943.659 121.424 941.966 124.9 940.973 128.721C940.105 132.063 942.911 135 946.364 135H963C963.254 130.685 964.951 127.919 968 125C971.176 122.081 975.537 120 981 120C986.336 120 990.951 121.462 994 124C997.049 126.412 999 129.938 999 134C999 136.031 998.271 137.604 997 139C995.729 140.269 993.287 141 991 141H975C964.454 141 956.48 143.542 950 149C943.647 154.331 940 161.608 940 171C940 176.458 941.332 181.558 944 186C946.668 190.315 950.299 193.462 955 196C959.828 198.412 965.901 200 972 200C978.607 200 984.172 198.667 989 196.002C993.955 193.21 997.348 189.442 999 185V200H1025V137C1025 129.892 1022.68 123.331 1019 118C1015.44 112.542 1011.35 107.919 1005 105ZM993.173 174.679C989.615 178.74 984.66 180.771 978.307 180.771C974.623 180.771 971.573 179.819 969.159 177.915C966.745 175.885 965.538 173.283 965.538 170.11C965.538 166.429 966.809 163.446 969.35 161.162C971.891 158.877 975.194 157.735 979.26 157.735H998.7V159.067C998.7 165.413 996.857 170.617 993.173 174.679Z" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -11,23 +11,180 @@ defineProps({
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
headline: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full flex flex-col justify-between items-start bg-[#020420] p-20 pt-32 pb-16">
|
||||
<div
|
||||
style="position: absolute;width: 1156px;height: 1000px;left: -215px;top: -337px;background: radial-gradient(50% 50% at 50% 50%, #00DC82 0%, rgba(0, 220, 130, 0) 100%);filter: blur(180.5px);opacity: 0.5;"
|
||||
/>
|
||||
<div>
|
||||
<h1 class="text-8xl mb-4 text-white">
|
||||
{{ title }}
|
||||
<div class="w-full h-full flex flex-col justify-center bg-slate-900">
|
||||
<svg
|
||||
class="absolute right-0 top-0"
|
||||
width="629"
|
||||
height="593"
|
||||
viewBox="0 0 629 593"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_f_199_94966)">
|
||||
<path d="M628.5 -578L639.334 -94.4223L806.598 -548.281L659.827 -87.387L965.396 -462.344L676.925 -74.0787L1087.69 -329.501L688.776 -55.9396L1160.22 -164.149L694.095 -34.9354L1175.13 15.7948L692.306 -13.3422L1130.8 190.83L683.602 6.50012L1032.04 341.989L668.927 22.4412L889.557 452.891L649.872 32.7537L718.78 511.519L628.5 36.32L538.22 511.519L607.128 32.7537L367.443 452.891L588.073 22.4412L224.955 341.989L573.398 6.50012L126.198 190.83L564.694 -13.3422L81.8734 15.7948L562.905 -34.9354L96.7839 -164.149L568.224 -55.9396L169.314 -329.501L580.075 -74.0787L291.604 -462.344L597.173 -87.387L450.402 -548.281L617.666 -94.4223L628.5 -578Z" fill="#00DC82" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_199_94966"
|
||||
x="0.873535"
|
||||
y="-659"
|
||||
width="1255.25"
|
||||
height="1251.52"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="40.5" result="effect1_foregroundBlur_199_94966" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div class="w-[700px] pl-[100px]">
|
||||
<p v-if="headline" class="uppercase text-[24px] text-[#00DC82] mb-4 font-semibold">
|
||||
{{ headline }}
|
||||
</p>
|
||||
<h1 class="m-0 text-[75px] font-semibold mb-2 text-white flex items-center">
|
||||
<span>{{ title }}</span>
|
||||
</h1>
|
||||
<p class="text-5xl text-gray-200 leading-tight pr-10">
|
||||
{{ description }}
|
||||
<p class="text-[32px] text-[#94a3b8] leading-tight">
|
||||
{{ description.slice(0, 200) }}
|
||||
</p>
|
||||
</div>
|
||||
<LogoGreen class="w-[306px] h-[60px] text-white" />
|
||||
|
||||
<svg
|
||||
class="absolute top-[160px] right-[90px]"
|
||||
width="340"
|
||||
height="340"
|
||||
viewBox="0 0 340 340"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M86.6286 103.106C88.2099 94.7477 94.7477 88.2099 103.106 86.6286L104.427 86.3788C146.343 78.4485 189.386 78.5576 231.262 86.7002L232.272 86.8967C239.615 88.3244 245.4 93.99 246.981 101.301L247.277 102.671C256.565 145.63 256.438 190.092 246.903 232.997C245.361 239.939 239.939 245.361 232.997 246.903C190.092 256.438 145.63 256.565 102.671 247.277L101.301 246.981C93.99 245.4 88.3244 239.615 86.8967 232.272L86.7002 231.262C78.5576 189.386 78.4485 146.343 86.3788 104.426L86.6286 103.106Z" fill="url(#paint0_linear_199_94959)" />
|
||||
<path d="M86.6286 103.106C88.2099 94.7477 94.7477 88.2099 103.106 86.6286L104.427 86.3788C146.343 78.4485 189.386 78.5576 231.262 86.7002L232.272 86.8967C239.615 88.3244 245.4 93.99 246.981 101.301L247.277 102.671C256.565 145.63 256.438 190.092 246.903 232.997C245.361 239.939 239.939 245.361 232.997 246.903C190.092 256.438 145.63 256.565 102.671 247.277L101.301 246.981C93.99 245.4 88.3244 239.615 86.8967 232.272L86.7002 231.262C78.5576 189.386 78.4485 146.343 86.3788 104.426L86.6286 103.106Z" fill="url(#paint1_radial_199_94959)" fill-opacity="0.06" />
|
||||
<path d="M103.028 86.2151C94.4994 87.8286 87.8286 94.4994 86.2151 103.028L85.9653 104.348C78.0252 146.318 78.1344 189.414 86.2872 231.342L86.4836 232.353C87.9434 239.86 93.7366 245.776 101.212 247.392L102.582 247.688C145.601 256.989 190.124 256.862 233.089 247.314C240.19 245.736 245.736 240.19 247.314 233.089C256.862 190.124 256.989 145.601 247.688 102.582L247.392 101.212C245.776 93.7366 239.86 87.9434 232.353 86.4836L231.342 86.2872C189.414 78.1344 146.318 78.0252 104.348 85.9653L103.028 86.2151Z" stroke="url(#paint2_linear_199_94959)" stroke-opacity="0.2" stroke-width="0.841584" />
|
||||
<path d="M86.5693 66.0418C76.1888 68.0728 68.0728 76.1888 66.0418 86.5693L65.7807 87.9041C55.4495 140.708 55.5631 195.024 66.1151 247.784L66.3095 248.756C68.1871 258.144 75.4486 265.527 84.8039 267.561L86.1767 267.86C140.001 279.561 195.755 279.425 249.525 267.477C258.483 265.486 265.485 258.489 267.475 249.53C279.429 195.737 279.558 139.989 267.852 86.1413L267.553 84.7671C265.524 75.4341 258.158 68.19 248.793 66.3169L247.784 66.1151C195.024 55.5631 140.708 55.4495 87.904 65.7807L86.5693 66.0418Z" stroke="url(#paint3_linear_199_94959)" stroke-opacity="0.6" stroke-width="1.7" />
|
||||
<path d="M66.1749 44.2983C55.2117 46.6476 46.6475 55.2117 44.2983 66.1749L43.1523 71.5228C29.5422 135.037 29.5422 200.713 43.1523 264.227L44.425 270.166C46.7026 280.795 54.917 289.15 65.5055 291.608C132.857 307.243 203.127 307.189 270.477 291.554C280.924 289.129 289.129 280.924 291.554 270.477C307.189 203.127 307.243 132.857 291.608 65.5055C289.15 54.917 280.795 46.7026 270.166 44.425L264.227 43.1523C200.713 29.5422 135.037 29.5422 71.5228 43.1523L66.1749 44.2983Z" stroke="url(#paint4_linear_199_94959)" stroke-opacity="0.4" stroke-width="1.7" />
|
||||
<path d="M47.0949 24.9846C36.0029 27.3375 27.3375 36.0029 24.9846 47.0949L19.3193 73.8027C5.86738 137.219 5.96967 202.762 19.6195 266.137L25.0028 291.131C27.3518 302.037 35.8106 310.592 46.6894 313.064L67.2583 317.739C134.879 333.107 205.098 332.991 272.667 317.398L291.54 313.043C302.229 310.576 310.576 302.229 313.043 291.54L317.398 272.667C332.991 205.098 333.107 134.879 317.739 67.2583L313.064 46.6894C310.592 35.8106 302.037 27.3518 291.131 25.0028L266.137 19.6195C202.762 5.96966 137.219 5.86738 73.8027 19.3193L47.0949 24.9846Z" stroke="url(#paint5_linear_199_94959)" stroke-opacity="0.2" stroke-width="2.125" />
|
||||
<path d="M174.667 190.325H203.105C204.009 190.325 204.896 190.084 205.678 189.626C206.461 189.168 207.11 188.509 207.561 187.715C208.013 186.921 208.25 186.021 208.25 185.105C208.25 184.188 208.011 183.288 207.559 182.495L188.461 148.938C188.009 148.145 187.36 147.486 186.578 147.028C185.796 146.57 184.909 146.328 184.006 146.328C183.103 146.328 182.215 146.57 181.433 147.028C180.651 147.486 180.002 148.145 179.551 148.938L174.667 157.524L165.119 140.734C164.668 139.941 164.018 139.282 163.236 138.824C162.453 138.366 161.566 138.125 160.663 138.125C159.76 138.125 158.872 138.366 158.09 138.824C157.308 139.282 156.658 139.941 156.206 140.734L132.441 182.495C131.989 183.288 131.75 184.188 131.75 185.105C131.75 186.021 131.987 186.921 132.439 187.715C132.89 188.509 133.539 189.168 134.322 189.626C135.104 190.084 135.991 190.325 136.895 190.325H154.746C161.819 190.325 167.035 187.173 170.624 181.025L179.337 165.717L184.004 157.524L198.011 182.133H179.337L174.667 190.325ZM154.455 182.124L141.997 182.121L160.671 149.312L169.989 165.717L163.75 176.681C161.367 180.671 158.659 182.124 154.455 182.124Z" fill="#00DC82" />
|
||||
<path d="M176.761 189.109H203.105H203.106C203.792 189.109 204.467 188.926 205.064 188.576C205.66 188.227 206.158 187.723 206.504 187.113L207.561 187.715L206.504 187.113C206.851 186.504 207.034 185.811 207.034 185.105C207.033 184.399 206.85 183.707 206.502 183.098L206.502 183.097L187.404 149.54L187.404 149.54C187.057 148.93 186.56 148.427 185.963 148.077C185.367 147.728 184.692 147.545 184.006 147.545C183.32 147.545 182.645 147.728 182.048 148.077C181.452 148.427 180.954 148.93 180.608 149.539C180.608 149.539 180.608 149.54 180.608 149.54L175.725 158.126L174.667 159.985L173.61 158.125L164.062 141.336L176.761 189.109ZM176.761 189.109L180.044 183.349H198.011H200.103L199.069 181.531L185.061 156.922L184.005 155.066L182.947 156.922L178.28 165.115L178.28 165.115L169.57 180.417C166.187 186.208 161.362 189.109 154.746 189.109H136.895H136.894C136.208 189.109 135.533 188.926 134.936 188.576C134.34 188.227 133.842 187.723 133.496 187.113L132.44 187.714L133.496 187.113C133.149 186.504 132.966 185.811 132.966 185.105C132.967 184.399 133.15 183.707 133.498 183.098L133.498 183.097L157.263 141.336C157.263 141.336 157.263 141.336 157.263 141.336C157.61 140.727 158.108 140.223 158.705 139.874C159.301 139.525 159.976 139.341 160.663 139.341C161.349 139.341 162.025 139.525 162.621 139.874L163.236 138.824L162.621 139.874C163.218 140.223 163.715 140.727 164.062 141.336L176.761 189.109ZM154.454 183.34H154.455C156.699 183.34 158.653 182.952 160.391 181.953C162.126 180.957 163.533 179.417 164.794 177.305L164.801 177.294L164.807 177.283L171.046 166.318L171.388 165.717L171.047 165.116L161.729 148.711L160.672 146.851L159.614 148.71L140.94 181.52L139.905 183.337L141.997 183.338L154.454 183.34Z" stroke="url(#paint6_linear_199_94959)" stroke-opacity="0.2" stroke-width="2.43271" />
|
||||
<g filter="url(#filter0_f_199_94959)">
|
||||
<path d="M174.667 190.325H203.105C204.009 190.325 204.896 190.084 205.678 189.626C206.461 189.168 207.11 188.509 207.561 187.715C208.013 186.921 208.25 186.021 208.25 185.105C208.25 184.188 208.011 183.288 207.559 182.495L188.461 148.938C188.009 148.145 187.36 147.486 186.578 147.028C185.796 146.57 184.909 146.328 184.006 146.328C183.103 146.328 182.215 146.57 181.433 147.028C180.651 147.486 180.002 148.145 179.551 148.938L174.667 157.524L165.119 140.734C164.668 139.941 164.018 139.282 163.236 138.824C162.453 138.366 161.566 138.125 160.663 138.125C159.76 138.125 158.872 138.366 158.09 138.824C157.308 139.282 156.658 139.941 156.206 140.734L132.441 182.495C131.989 183.288 131.75 184.188 131.75 185.105C131.75 186.021 131.987 186.921 132.439 187.715C132.89 188.509 133.539 189.168 134.322 189.626C135.104 190.084 135.991 190.325 136.895 190.325H154.746C161.819 190.325 167.035 187.173 170.624 181.025L179.337 165.717L184.004 157.524L198.011 182.133H179.337L174.667 190.325ZM154.455 182.124L141.997 182.121L160.671 149.312L169.989 165.717L163.75 176.681C161.367 180.671 158.659 182.124 154.455 182.124Z" fill="#00DC82" />
|
||||
<path d="M176.761 189.109H203.105H203.106C203.792 189.109 204.467 188.926 205.064 188.576C205.66 188.227 206.158 187.723 206.504 187.113L207.561 187.715L206.504 187.113C206.851 186.504 207.034 185.811 207.034 185.105C207.033 184.399 206.85 183.707 206.502 183.098L206.502 183.097L187.404 149.54L187.404 149.54C187.057 148.93 186.56 148.427 185.963 148.077C185.367 147.728 184.692 147.545 184.006 147.545C183.32 147.545 182.645 147.728 182.048 148.077C181.452 148.427 180.954 148.93 180.608 149.539C180.608 149.539 180.608 149.54 180.608 149.54L175.725 158.126L174.667 159.985L173.61 158.125L164.062 141.336L176.761 189.109ZM176.761 189.109L180.044 183.349H198.011H200.103L199.069 181.531L185.061 156.922L184.005 155.066L182.947 156.922L178.28 165.115L178.28 165.115L169.57 180.417C166.187 186.208 161.362 189.109 154.746 189.109H136.895H136.894C136.208 189.109 135.533 188.926 134.936 188.576C134.34 188.227 133.842 187.723 133.496 187.113L132.44 187.714L133.496 187.113C133.149 186.504 132.966 185.811 132.966 185.105C132.967 184.399 133.15 183.707 133.498 183.098L133.498 183.097L157.263 141.336C157.263 141.336 157.263 141.336 157.263 141.336C157.61 140.727 158.108 140.223 158.705 139.874C159.301 139.525 159.976 139.341 160.663 139.341C161.349 139.341 162.025 139.525 162.621 139.874L163.236 138.824L162.621 139.874C163.218 140.223 163.715 140.727 164.062 141.336L176.761 189.109ZM154.454 183.34H154.455C156.699 183.34 158.653 182.952 160.391 181.953C162.126 180.957 163.533 179.417 164.794 177.305L164.801 177.294L164.807 177.283L171.046 166.318L171.388 165.717L171.047 165.116L161.729 148.711L160.672 146.851L159.614 148.71L140.94 181.52L139.905 183.337L141.997 183.338L154.454 183.34Z" stroke="url(#paint7_linear_199_94959)" stroke-opacity="0.2" stroke-width="2.43271" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_199_94959"
|
||||
x="124.176"
|
||||
y="130.551"
|
||||
width="91.6485"
|
||||
height="67.3485"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="3.78713" result="effect1_foregroundBlur_199_94959" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_199_94959"
|
||||
x1="167.875"
|
||||
y1="74.375"
|
||||
x2="88"
|
||||
y2="261"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#0F172A" />
|
||||
<stop offset="1" stop-color="#0F172A" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint1_radial_199_94959"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(167.875 167.875) rotate(-90) scale(100.596 107.502)"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_199_94959"
|
||||
x1="247.183"
|
||||
y1="96.4978"
|
||||
x2="194.172"
|
||||
y2="229.234"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_199_94959"
|
||||
x1="267.01"
|
||||
y1="78.6537"
|
||||
x2="200.746"
|
||||
y2="244.574"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#00DC82" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_199_94959"
|
||||
x1="292.405"
|
||||
y1="57.8159"
|
||||
x2="209.877"
|
||||
y2="264.463"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#00DC82" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_199_94959"
|
||||
x1="314.196"
|
||||
y1="40.2232"
|
||||
x2="217.813"
|
||||
y2="281.562"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#00DC82" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint6_linear_199_94959"
|
||||
x1="202.444"
|
||||
y1="144.3"
|
||||
x2="191.546"
|
||||
y2="184.293"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint7_linear_199_94959"
|
||||
x1="202.444"
|
||||
y1="144.3"
|
||||
x2="191.546"
|
||||
y2="184.293"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex border border-b-0 border-gray-200 dark:border-gray-700 relative not-prose" :class="[{ 'p-4': padding }, propsToSelect.length ? 'border-t-0' : 'rounded-t-md', backgroundClass, overflowClass]">
|
||||
<component :is="name" v-model="vModel" v-bind="fullProps">
|
||||
<div class="flex border border-b-0 border-gray-200 dark:border-gray-700 relative not-prose" :class="[{ 'p-4': padding }, propsToSelect.length ? 'border-t-0' : 'rounded-t-md', backgroundClass, extraClass]">
|
||||
<component :is="name" v-model="vModel" v-bind="fullProps" :class="componentClass">
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<ContentSlot :name="slot" />
|
||||
<ContentSlot :name="slot" unwrap="p" />
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
@@ -56,7 +56,6 @@ import { transformContent } from '@nuxt/content/transformers'
|
||||
// @ts-ignore
|
||||
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
|
||||
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||
import * as config from '#ui/ui.config'
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const props = defineProps({
|
||||
@@ -92,21 +91,25 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
extraColors: {
|
||||
type: Array,
|
||||
options: {
|
||||
type: Array as PropType<{ name: string; values: string[]; restriction: 'expected' | 'included' | 'excluded' | 'only' }[]>,
|
||||
default: () => []
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-white dark:bg-gray-900'
|
||||
},
|
||||
overflowClass: {
|
||||
extraClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
previewOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
componentClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -114,20 +117,25 @@ const props = defineProps({
|
||||
const baseProps = reactive({ ...props.baseProps })
|
||||
const componentProps = reactive({ ...props.props })
|
||||
|
||||
const { $prettier } = useNuxtApp()
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = camelCase(slug)
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
let name = props.slug || `U${upperFirst(camelCase(route.params.slug[route.params.slug.length - 1]))}`
|
||||
|
||||
// TODO: Remove once merged on `main` branch
|
||||
if (['AvatarGroup', 'ButtonGroup', 'MeterGroup'].includes(name)) {
|
||||
name = `U${name}`
|
||||
}
|
||||
if (['avatar-group', 'button-group', 'radio'].includes(name)) {
|
||||
name = `U${upperFirst(camelCase(name))}`
|
||||
}
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
|
||||
// Computed
|
||||
|
||||
const ui = computed(() => ({ ...config[camelName], ...props.ui }))
|
||||
|
||||
const fullProps = computed(() => ({ ...baseProps, ...componentProps }))
|
||||
const fullProps = computed(() => ({ ...componentProps, ...baseProps }))
|
||||
const vModel = computed({
|
||||
get: () => baseProps.modelValue,
|
||||
set: (value) => {
|
||||
@@ -135,19 +143,64 @@ const vModel = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const generateOptions = (key: string, schema: { kind: string, schema: [], type: string }) => {
|
||||
let options = []
|
||||
const optionItem = props?.options?.find(item => item?.name === key) || null
|
||||
const types = schema?.type?.split('|')?.map(item => item.trim()?.replaceAll('"', '')) || []
|
||||
const hasIgnoredTypes = types?.every(item => ['string', 'number', 'boolean', 'array', 'object', 'Function'].includes(item))
|
||||
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
options = [...appConfig.ui.colors]
|
||||
}
|
||||
|
||||
if (key.toLowerCase() === 'size' && schema?.schema?.length > 0) {
|
||||
const baseSizeOrder = { 'xs': 1, 'sm': 2, 'md': 3, 'lg': 4, 'xl': 5 }
|
||||
schema.schema.sort((a: string, b: string) => {
|
||||
const aBase = a.match(/[a-zA-Z]+/)[0].toLowerCase()
|
||||
const bBase = b.match(/[a-zA-Z]+/)[0].toLowerCase()
|
||||
|
||||
const aNum = parseInt(a.match(/\d+/)?.[0]) || 1
|
||||
const bNum = parseInt(b.match(/\d+/)?.[0]) || 1
|
||||
|
||||
if (aBase === bBase) {
|
||||
return aBase === 'xs' ? bNum - aNum : aNum - bNum
|
||||
}
|
||||
|
||||
return baseSizeOrder[aBase] - baseSizeOrder[bBase]
|
||||
})
|
||||
}
|
||||
|
||||
if (schema?.schema?.length > 0 && schema?.kind === 'enum' && !hasIgnoredTypes && optionItem?.restriction !== 'only') {
|
||||
options = schema.schema.filter(option => typeof option === 'string').map((option: string) => option.replaceAll('"', ''))
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'only') {
|
||||
options = optionItem.values
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'expected') {
|
||||
options = options.filter(item => optionItem.values.includes(item))
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'included') {
|
||||
options = [...options, ...optionItem.values]
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'excluded') {
|
||||
options = options.filter(item => !optionItem.values.includes(item))
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
if (props.excludedProps.includes(key)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
|
||||
const dottedKey = kebabCase(key).replaceAll('-', '.')
|
||||
const keys = ui.value[dottedKey] ?? {}
|
||||
let options = typeof keys === 'object' && Object.keys(keys)
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
// @ts-ignore
|
||||
options = [...appConfig.ui.colors, ...props.extraColors]
|
||||
}
|
||||
const schema = prop?.schema || {}
|
||||
const options = generateOptions(key, schema)
|
||||
|
||||
return {
|
||||
type: prop?.type || 'string',
|
||||
@@ -161,12 +214,12 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
const code = computed(() => {
|
||||
let code = `\`\`\`html
|
||||
<${name}`
|
||||
for (const [key, value] of Object.entries(componentProps)) {
|
||||
for (const [key, value] of Object.entries(fullProps.value)) {
|
||||
if (value === 'undefined' || value === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
code += ` ${(typeof value === 'boolean' && value !== true) || typeof value === 'object' || typeof value === 'number' ? ':' : ''}${key === 'modelValue' ? 'value' : kebabCase(key)}${typeof value === 'boolean' && !!value && key !== 'modelValue' ? '' : `="${typeof value === 'object' ? renderObject(value) : value}"`}`
|
||||
code += ` ${(typeof value === 'boolean' && (value !== true || key === 'modelValue')) || typeof value === 'object' || typeof value === 'number' ? ':' : ''}${key === 'modelValue' ? 'model-value' : kebabCase(key)}${typeof value === 'boolean' && !!value && key !== 'modelValue' ? '' : `="${typeof value === 'object' ? renderObject(value) : value}"`}`
|
||||
}
|
||||
|
||||
if (props.slots) {
|
||||
@@ -181,7 +234,7 @@ const code = computed(() => {
|
||||
code += `>
|
||||
${props.code}</${name}>`
|
||||
} else {
|
||||
code += `>${props.code}</${name}>`
|
||||
code += `>${props.code.endsWith('>') ? `${props.code}\n` : props.code}</${name}>`
|
||||
}
|
||||
} else {
|
||||
code += ' />'
|
||||
@@ -210,16 +263,27 @@ function renderObject (obj: any) {
|
||||
|
||||
const shikiHighlighter = useShikiHighlighter({})
|
||||
const codeHighlighter = async (code: string, lang: string, theme: any, highlights: number[]) => shikiHighlighter.getHighlightedAST(code, lang, theme, { highlights })
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify({ props: componentProps, slots: props.slots })}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
const { data: ast } = await useAsyncData(
|
||||
`${name}-ast-${JSON.stringify({ props: componentProps, slots: props.slots, code: props.code })}`,
|
||||
async () => {
|
||||
let formatted = ''
|
||||
try {
|
||||
formatted = await $prettier.format(code.value) || code.value
|
||||
} catch (error) {
|
||||
formatted = code.value
|
||||
}
|
||||
}
|
||||
}), { watch: [code] })
|
||||
|
||||
return transformContent('content:_markdown.md', formatted, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}, { watch: [code] })
|
||||
</script>
|
||||
|
||||
@@ -1,26 +1,98 @@
|
||||
<template>
|
||||
<div class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 relative not-prose rounded-t-md" :class="[{ 'p-4': padding, 'rounded-b-md': !$slots.code, 'border-b-0': !!$slots.code }, backgroundClass, overflowClass]">
|
||||
<div
|
||||
class="flex border border-gray-200 dark:border-gray-700 relative rounded-t-md"
|
||||
:class="[{ 'p-4': padding, 'rounded-b-md': !hasCode, 'border-b-0': hasCode, 'not-prose': !prose }, backgroundClass, extraClass]"
|
||||
>
|
||||
<template v-if="component">
|
||||
<iframe v-if="iframe" :src="`/examples/${component}`" v-bind="iframeProps" :class="backgroundClass" class="w-full" />
|
||||
<component :is="camelName" v-else v-bind="componentProps" :class="componentClass" />
|
||||
</template>
|
||||
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
</div>
|
||||
|
||||
<ContentSlot v-if="$slots.code" :use="$slots.code" />
|
||||
<template v-if="hasCode">
|
||||
<ContentSlot v-if="$slots.code" :use="$slots.code" />
|
||||
<ContentRenderer v-else :value="ast" class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
import { camelCase } from 'scule'
|
||||
import { fetchContentExampleCode } from '~/composables/useContentExamplesCode'
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
// @ts-ignore
|
||||
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
|
||||
|
||||
const props = defineProps({
|
||||
component: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
componentClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
componentProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
hiddenCode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
padding: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
prose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
iframe: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
iframeProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-white dark:bg-gray-900'
|
||||
},
|
||||
overflowClass: {
|
||||
extraClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
let component = props.component
|
||||
// TODO: Remove once merged on `main` branch
|
||||
if (['command-palette-theme-algolia', 'command-palette-theme-raycast', 'vertical-navigation-theme-tailwind', 'pagination-theme-rounded'].includes(component)) {
|
||||
component = component.replace('-theme', '-example-theme')
|
||||
}
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
const camelName = camelCase(component)
|
||||
const data = await fetchContentExampleCode(camelName)
|
||||
|
||||
const hasCode = computed(() => !props.hiddenCode && (data?.code || instance.slots.code))
|
||||
|
||||
const shikiHighlighter = useShikiHighlighter({})
|
||||
const codeHighlighter = async (code: string, lang: string, theme: any, highlights: number[]) => shikiHighlighter.getHighlightedAST(code, lang, theme, { highlights })
|
||||
const { data: ast } = await useAsyncData(`content-example-${camelName}-ast`, () => transformContent('content:_markdown.md', `\`\`\`vue\n${data?.code ?? ''}\n\`\`\``, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
</script>
|
||||
|
||||
@@ -17,10 +17,16 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = camelCase(slug)
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
let name = props.slug || `U${upperFirst(camelCase(route.params.slug[route.params.slug.length - 1]))}`
|
||||
|
||||
// TODO: Remove once merged on `main` branch
|
||||
if (['AvatarGroup', 'ButtonGroup', 'MeterGroup'].includes(name)) {
|
||||
name = `U${name}`
|
||||
}
|
||||
if (['avatar-group', 'button-group', 'radio'].includes(name)) {
|
||||
name = `U${upperFirst(camelCase(name))}`
|
||||
}
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
</script>
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<template>
|
||||
<Field v-bind="prop">
|
||||
<code v-if="prop.default">{{ prop.default }}</code>
|
||||
<p v-if="prop.description">
|
||||
{{ prop.description }}
|
||||
</p>
|
||||
|
||||
<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">
|
||||
<FieldGroup v-for="schema in prop.schema.schema" :key="schema.name">
|
||||
<ComponentPropsField v-for="subProp in Object.values(schema.schema)" :key="(subProp as any).name" :prop="subProp" />
|
||||
</FieldGroup>
|
||||
</Collapsible>
|
||||
<Collapsible v-else-if="prop.schema?.kind === 'array' && prop.schema?.schema?.filter(schema => schema.kind === 'array').length">
|
||||
<FieldGroup v-for="schema in prop.schema.schema" :key="schema.name" class="!mt-0">
|
||||
<FieldGroup v-for="schema in prop.schema.schema" :key="schema.name">
|
||||
<template v-for="subSchema in schema.schema" :key="subSchema.name">
|
||||
<ComponentPropsField v-for="subProp in Object.values(subSchema.schema)" :key="(subProp as any).name" :prop="subProp" />
|
||||
</template>
|
||||
</FieldGroup>
|
||||
</Collapsible>
|
||||
<Collapsible v-else-if="prop.schema?.kind === 'object' && prop.schema.type !== 'Function' && Object.values(prop.schema.schema)?.length">
|
||||
<FieldGroup class="!mt-0">
|
||||
<FieldGroup>
|
||||
<ComponentPropsField v-for="subProp in Object.values(prop.schema.schema)" :key="(subProp as any).name" :prop="subProp" />
|
||||
</FieldGroup>
|
||||
</Collapsible>
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slot</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="slot in (meta.meta.slots as any[])" :key="slot.name">
|
||||
<td class="whitespace-nowrap">
|
||||
<code>{{ slot.name }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<FieldGroup>
|
||||
<Field v-for="slot in meta?.meta.slots" :key="slot.name" v-bind="slot" />
|
||||
</FieldGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -28,10 +17,8 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = camelCase(slug)
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const name = props.slug || `U${upperFirst(camelCase(route.params.slug[route.params.slug.length - 1]))}`
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded border border-dashed border-gray-400 dark:border-gray-500 opacity-75">
|
||||
<div class="relative overflow-hidden rounded border border-dashed border-gray-400 dark:border-gray-500 opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-gray-900/10 dark:stroke-white/10" fill="none">
|
||||
<defs>
|
||||
<pattern
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<iframe :src="src" class="w-full min-h-[calc(100vh/1.5)] border border-gray-200 dark:border-gray-800 rounded-md" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
token: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const src = computed(() => `https://volta.net/embed/${props.token}?theme=${colorMode.value}&gray=${appConfig.ui.gray}&primary=${appConfig.ui.primary}`)
|
||||
</script>
|
||||
17
docs/components/content/examples/BreadcrumbExampleBasic.vue
Normal file
17
docs/components/content/examples/BreadcrumbExampleBasic.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Home',
|
||||
icon: 'i-heroicons-home',
|
||||
to: '/'
|
||||
}, {
|
||||
label: 'Navigation',
|
||||
icon: 'i-heroicons-square-3-stack-3d'
|
||||
}, {
|
||||
label: 'Breadcrumb',
|
||||
icon: 'i-heroicons-link'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBreadcrumb :links="links" />
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Home',
|
||||
to: '/'
|
||||
}, {
|
||||
label: 'Navigation'
|
||||
}, {
|
||||
label: 'Breadcrumb'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBreadcrumb :links="links">
|
||||
<template #default="{ link, isActive, index }">
|
||||
<UBadge :color="isActive ? 'primary' : 'gray'" class="rounded-full">
|
||||
{{ index + 1 }}. {{ link.label }}
|
||||
</UBadge>
|
||||
</template>
|
||||
</UBreadcrumb>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Home',
|
||||
icon: 'i-heroicons-home',
|
||||
to: '/'
|
||||
}, {
|
||||
label: 'Navigation',
|
||||
icon: 'i-heroicons-square-3-stack-3d'
|
||||
}, {
|
||||
label: 'Breadcrumb',
|
||||
icon: 'i-heroicons-link'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBreadcrumb :links="links" :ui="{ ol: 'gap-x-3', li: 'gap-x-3' }">
|
||||
<template #divider>
|
||||
<span class="w-8 h-1 rounded-full bg-gray-300 dark:bg-gray-700" />
|
||||
</template>
|
||||
</UBreadcrumb>
|
||||
</template>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Home',
|
||||
to: '/'
|
||||
}, {
|
||||
label: 'Navigation'
|
||||
}, {
|
||||
label: 'Breadcrumb'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBreadcrumb :links="links" :divider="null" :ui="{ ol: 'gap-x-3' }">
|
||||
<template #icon="{ link, index, isActive }">
|
||||
<UAvatar
|
||||
:alt="(index + 1 ).toString()"
|
||||
:ui="{
|
||||
background: isActive ? 'bg-primary-500 dark:bg-primary-400' : undefined,
|
||||
placeholder: isActive ? 'text-white dark:text-gray-900' : !!link.to ? 'group-hover:text-gray-700 dark:group-hover:text-gray-200' : ''
|
||||
}"
|
||||
size="xs"
|
||||
/>
|
||||
</template>
|
||||
</UBreadcrumb>
|
||||
</template>
|
||||
19
docs/components/content/examples/ChipExampleContentSlot.vue
Normal file
19
docs/components/content/examples/ChipExampleContentSlot.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<UChip size="md" position="bottom-right" inset :ui="{ base: '-mx-2 rounded-none ring-0', background: '' }">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/739984?v=4"
|
||||
alt="Avatar"
|
||||
size="lg"
|
||||
/>
|
||||
|
||||
<template #content>
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/in/80442?v=4"
|
||||
alt="Avatar"
|
||||
size="xs"
|
||||
:ui="{ rounded: 'rounded-md' }"
|
||||
class="shadow-md"
|
||||
/>
|
||||
</template>
|
||||
</UChip>
|
||||
</template>
|
||||
19
docs/components/content/examples/ChipExampleShow.vue
Normal file
19
docs/components/content/examples/ChipExampleShow.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
const items = [{
|
||||
name: 'messages',
|
||||
icon: 'i-heroicons-chat-bubble-oval-left',
|
||||
count: 3
|
||||
}, {
|
||||
name: 'notifications',
|
||||
icon: 'i-heroicons-bell',
|
||||
count: 0
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-3">
|
||||
<UChip v-for="{ name, icon, count } in items" :key="name" :show="count > 0">
|
||||
<UButton :icon="icon" color="gray" />
|
||||
</UChip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,19 +1,17 @@
|
||||
<script setup>
|
||||
const groups = computed(() => {
|
||||
return [{
|
||||
key: 'users',
|
||||
label: q => q && `Users matching “${q}”...`,
|
||||
search: async (q) => {
|
||||
if (!q) {
|
||||
return []
|
||||
}
|
||||
|
||||
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
const groups = [{
|
||||
key: 'users',
|
||||
label: q => q && `Users matching “${q}”...`,
|
||||
search: async (q) => {
|
||||
if (!q) {
|
||||
return []
|
||||
}
|
||||
}].filter(Boolean)
|
||||
})
|
||||
|
||||
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
}
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper', child: true },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb', child: true },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox', child: true },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz', child: true },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham', child: true },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const groups = [{
|
||||
key: 'users',
|
||||
commands: people,
|
||||
filter: (q, commands) => {
|
||||
if (!q) {
|
||||
return commands?.filter(command => !command.child)
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette :groups="groups" :autoselect="false" />
|
||||
</template>
|
||||
35
docs/components/content/examples/ContextMenuExampleArrow.vue
Normal file
35
docs/components/content/examples/ContextMenuExampleArrow.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="onContextMenu">
|
||||
<Placeholder class="h-96 select-none w-full flex items-center justify-center">
|
||||
Right click here
|
||||
</Placeholder>
|
||||
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ arrow: true, placement: 'right' }">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="onContextMenu">
|
||||
<Placeholder class="h-96 select-none w-full flex items-center justify-center">
|
||||
Right click here
|
||||
</Placeholder>
|
||||
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ offset: 0 }">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="onContextMenu">
|
||||
<Placeholder class="h-96 select-none w-full flex items-center justify-center">
|
||||
Right click here
|
||||
</Placeholder>
|
||||
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ placement: 'right-start' }">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UDivider>
|
||||
<Logo class="w-28 h-6" />
|
||||
</UDivider>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
const form = reactive({ email: 'mail@example.com', password: 'password' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex flex-col gap-y-4">
|
||||
<UCard :ui="{ body: { base: 'grid grid-cols-3' } }">
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="form.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="form.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton label="Login" color="gray" block />
|
||||
</div>
|
||||
|
||||
<UDivider label="OR" color="gray" orientation="vertical" />
|
||||
|
||||
<div class="space-y-4 flex flex-col justify-center">
|
||||
<UButton color="black" label="Login with GitHub" icon="i-simple-icons-github" block />
|
||||
<UButton color="black" label="Login with Google" icon="i-simple-icons-google" block />
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="form.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="form.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton label="Login" color="gray" block />
|
||||
|
||||
<UDivider label="OR" color="gray" />
|
||||
|
||||
<UButton color="black" label="Login with GitHub" icon="i-simple-icons-github" block />
|
||||
<UButton color="black" label="Login with Google" icon="i-simple-icons-google" block />
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
16
docs/components/content/examples/DropdownExampleArrow.vue
Normal file
16
docs/components/content/examples/DropdownExampleArrow.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ arrow: true }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
16
docs/components/content/examples/DropdownExampleOffset.vue
Normal file
16
docs/components/content/examples/DropdownExampleOffset.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ offsetDistance: 0, placement: 'right-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ placement: 'right-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { FormError, FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormError, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
@@ -14,18 +13,14 @@ const validate = (state: any): FormError[] => {
|
||||
return errors
|
||||
}
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:validate="validate"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const options = [
|
||||
{ label: 'Option 1', value: 'option-1' },
|
||||
@@ -9,7 +8,7 @@ const options = [
|
||||
{ label: 'Option 3', value: 'option-3' }
|
||||
]
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
input: undefined,
|
||||
textarea: undefined,
|
||||
select: undefined,
|
||||
@@ -17,6 +16,7 @@ const state = ref({
|
||||
checkbox: undefined,
|
||||
toggle: undefined,
|
||||
radio: undefined,
|
||||
radioGroup: undefined,
|
||||
switch: undefined,
|
||||
range: undefined
|
||||
})
|
||||
@@ -39,6 +39,9 @@ const schema = z.object({
|
||||
radio: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
radioGroup: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
range: z.number().max(20, { message: 'Must be less than 20' })
|
||||
})
|
||||
|
||||
@@ -46,19 +49,14 @@ type Schema = z.infer<typeof schema>
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm ref="form" :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormGroup name="input" label="Input">
|
||||
<UInput v-model="state.input" />
|
||||
</UFormGroup>
|
||||
@@ -83,6 +81,10 @@ async function submit (event: FormSubmitEvent<Schema>) {
|
||||
<UCheckbox v-model="state.checkbox" label="Check me" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="radioGroup" label="Radio Group">
|
||||
<URadioGroup v-model="state.radioGroup" :options="options" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="radio" label="Radio">
|
||||
<URadio v-for="option in options" :key="option.value" v-model="state.radio" v-bind="option">
|
||||
{{ option.label }}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Joi from 'joi'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().required(),
|
||||
@@ -10,23 +9,19 @@ const schema = Joi.object({
|
||||
.required()
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
42
docs/components/content/examples/FormExampleOnError.vue
Normal file
42
docs/components/content/examples/FormExampleOnError.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormError, FormErrorEvent, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = reactive({
|
||||
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
|
||||
}
|
||||
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
|
||||
async function onError (event: FormErrorEvent) {
|
||||
const element = document.getElementById(event.errors[0].id)
|
||||
element?.focus()
|
||||
element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
|
||||
<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>
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { string, objectAsync, email, minLength, Input } from 'valibot'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import { string, objectAsync, email, minLength, type Input } from 'valibot'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = objectAsync({
|
||||
email: string([email('Invalid email')]),
|
||||
@@ -10,23 +9,19 @@ const schema = objectAsync({
|
||||
|
||||
type Schema = Input<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { object, string, InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import { object, string, type InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = object({
|
||||
email: string().email('Invalid email').required('Required'),
|
||||
@@ -12,23 +11,19 @@ const schema = object({
|
||||
|
||||
type Schema = InferType<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
@@ -10,23 +9,19 @@ const schema = z.object({
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4">
|
||||
<UFormGroup label="Username" name="username" eager-validation>
|
||||
<UInput v-model="state.username" placeholder="Choose Username" />
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { z } from 'zod'
|
||||
|
||||
const schema = z.object({
|
||||
username: z.string().min(10, 'Must be at least 10 characters')
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
username: undefined
|
||||
})
|
||||
</script>
|
||||
@@ -5,8 +5,9 @@
|
||||
</template>
|
||||
|
||||
<template #error="{ error }">
|
||||
<UAlert v-if="error" icon="i-heroicons-exclamation-triangle-20-solid" :title="error" color="red" />
|
||||
<UAlert v-else icon="i-heroicons-check-circle-20-solid" title="Your email is valid" color="green" />
|
||||
<span :class="[error ? 'text-red-500 dark:text-red-400' : 'text-primary-500 dark:text-primary-400']">
|
||||
{{ error ? error : 'Your email is valid' }}
|
||||
</span>
|
||||
</template>
|
||||
</UFormGroup>
|
||||
</template>
|
||||
|
||||
17
docs/components/content/examples/MeterGroupExampleSlots.vue
Normal file
17
docs/components/content/examples/MeterGroupExampleSlots.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<UMeterGroup :max="128">
|
||||
<template #indicator>
|
||||
<div class="flex gap-1.5 justify-between text-sm">
|
||||
<p>86GB used</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
42GB remaining
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UMeter :value="24" color="gray" label="System" icon="i-heroicons-cog-6-tooth" />
|
||||
<UMeter :value="8" color="red" label="Apps" icon="i-heroicons-window" />
|
||||
<UMeter :value="12" color="yellow" label="Documents" icon="i-heroicons-document" />
|
||||
<UMeter :value="42" color="green" label="Multimedia" icon="i-heroicons-film" />
|
||||
</UMeterGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const used = ref(84.2)
|
||||
|
||||
const total = 238.42
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMeter :value="used" :max="total">
|
||||
<template #indicator="{ percent }">
|
||||
<div class="text-sm text-right">
|
||||
{{ used }}GB used ({{ Math.round(percent) }}%)
|
||||
</div>
|
||||
</template>
|
||||
</UMeter>
|
||||
</template>
|
||||
15
docs/components/content/examples/MeterSlotLabelExample.vue
Normal file
15
docs/components/content/examples/MeterSlotLabelExample.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const used = ref(84.2)
|
||||
|
||||
const total = 238.42
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMeter :value="used" :max="total">
|
||||
<template #label="{ percent }">
|
||||
<p class="text-sm">
|
||||
You are using {{ Math.round(used) }}GB ({{ Math.round(100 - percent) }}%) of space
|
||||
</p>
|
||||
</template>
|
||||
</UMeter>
|
||||
</template>
|
||||
@@ -3,5 +3,5 @@ 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.' })" />
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'This is an <u>underlined</u> and <b>bold</b> notification.' })" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination v-model="page" :total="items.length" :ui="{ rounded: 'first-of-type:rounded-s-md last-of-type:rounded-e-md' }">
|
||||
<template #first="{ onClick }">
|
||||
<UTooltip text="First page">
|
||||
<UButton icon="i-heroicons-arrow-uturn-left" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:first-child]:rotate-180 me-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<template #last="{ onClick }">
|
||||
<UTooltip text="Last page">
|
||||
<UButton icon="i-heroicons-arrow-uturn-right-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:last-child]:rotate-180 ms-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
</UPagination>
|
||||
</template>
|
||||
11
docs/components/content/examples/PopoverExampleArrow.vue
Normal file
11
docs/components/content/examples/PopoverExampleArrow.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover :popper="{ arrow: true }">
|
||||
<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>
|
||||
11
docs/components/content/examples/PopoverExampleOffset.vue
Normal file
11
docs/components/content/examples/PopoverExampleOffset.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover :popper="{ offsetDistance: 0 }">
|
||||
<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>
|
||||
18
docs/components/content/examples/PopoverExampleOpen.vue
Normal file
18
docs/components/content/examples/PopoverExampleOpen.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-4 items-center">
|
||||
<UToggle v-model="open" />
|
||||
<UPopover :open="open">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
13
docs/components/content/examples/PopoverExampleOverlay.vue
Normal file
13
docs/components/content/examples/PopoverExampleOverlay.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="flex gap-4 items-center">
|
||||
<UPopover overlay>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
11
docs/components/content/examples/PopoverExamplePlacement.vue
Normal file
11
docs/components/content/examples/PopoverExamplePlacement.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover :popper="{ placement: 'top-end' }">
|
||||
<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>
|
||||
11
docs/components/content/examples/PopoverExampleSlot.vue
Normal file
11
docs/components/content/examples/PopoverExampleSlot.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<div class="p-8">
|
||||
<UButton label="Close" @click="close" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<UProgress class="progress">
|
||||
<template #indicator>
|
||||
<div class="text-right text-amber-500">
|
||||
🔥 This is too hot!
|
||||
</div>
|
||||
</template>
|
||||
</UProgress>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.progress:deep(progress:indeterminate.animation-default) {
|
||||
&:after {
|
||||
@apply w-full text-red-500;
|
||||
animation: my-glow-animation 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
@apply w-full text-red-500;
|
||||
animation: my-glow-animation 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
@apply w-full text-red-500;
|
||||
animation: my-glow-animation 3s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes my-glow-animation {
|
||||
50% {
|
||||
@apply text-amber-400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
const temp = ref(35)
|
||||
|
||||
const color = computed(() => {
|
||||
switch (true) {
|
||||
case temp.value < 10: return 'blue'
|
||||
case temp.value < 20: return 'amber'
|
||||
case temp.value < 30: return 'orange'
|
||||
default: return 'red'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UProgress :value="temp" :max="40" :color="color">
|
||||
<template #indicator="{ percent }">
|
||||
<div class="text-right" :style="{ width: `${percent}%` }">
|
||||
<span v-if="temp < 10" class="text-blue-500">Too cold!</span>
|
||||
<span v-else-if="temp < 20" class="text-amber-500">Warm</span>
|
||||
<span v-else-if="temp < 30" class="text-orange-500">Hot</span>
|
||||
<span v-else class="text-red-500 font-bold">🔥 Too hot!</span>
|
||||
</div>
|
||||
</template>
|
||||
</UProgress>
|
||||
</template>
|
||||
31
docs/components/content/examples/ProgressExampleSlotStep.vue
Normal file
31
docs/components/content/examples/ProgressExampleSlotStep.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
const task = ref(1)
|
||||
|
||||
const steps = [
|
||||
'Cloning...',
|
||||
'Migrating...',
|
||||
'Deploying...'
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UProgress :value="task" :max="steps" indicator>
|
||||
<template #step-0="{ step }">
|
||||
<span class="text-lime-500">
|
||||
<UIcon name="i-heroicons-arrow-down-circle" /> {{ step }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #step-1="{ step }">
|
||||
<span class="text-amber-500">
|
||||
<UIcon name="i-heroicons-circle-stack" /> {{ step }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #step-2="{ step }">
|
||||
<span class="text-blue-500">
|
||||
<UIcon name="i-heroicons-hand-thumb-up" /> {{ step }}
|
||||
</span>
|
||||
</template>
|
||||
</UProgress>
|
||||
</template>
|
||||
@@ -1,23 +1,15 @@
|
||||
<script setup>
|
||||
const methods = [{
|
||||
name: 'email',
|
||||
value: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
name: 'sms',
|
||||
value: 'sms',
|
||||
label: 'Phone (SMS)'
|
||||
}, {
|
||||
name: 'push',
|
||||
value: 'push',
|
||||
label: 'Push notification'
|
||||
}]
|
||||
const methods = [
|
||||
{ value: 'email', label: 'Email' },
|
||||
{ value: 'sms', label: 'Phone (SMS)' },
|
||||
{ value: 'push', label: 'Push notification' }
|
||||
]
|
||||
|
||||
const selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-1">
|
||||
<URadio v-for="method of methods" :key="method.name" v-model="selected" v-bind="method" />
|
||||
<URadio v-for="method of methods" :key="method.value" v-model="selected" v-bind="method" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
18
docs/components/content/examples/RadioGroupExample.vue
Normal file
18
docs/components/content/examples/RadioGroupExample.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
const options = [{
|
||||
value: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
value: 'sms',
|
||||
label: 'Phone (SMS)'
|
||||
}, {
|
||||
value: 'push',
|
||||
label: 'Push notification'
|
||||
}]
|
||||
|
||||
const selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URadioGroup v-model="selected" legend="Choose something" :options="options" />
|
||||
</template>
|
||||
20
docs/components/content/examples/RadioGroupLabelExample.vue
Normal file
20
docs/components/content/examples/RadioGroupLabelExample.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const options = [
|
||||
{ value: 'email', label: 'Email', icon: 'i-heroicons-at-symbol' },
|
||||
{ value: 'sms', label: 'Phone (SMS)', icon: 'i-heroicons-phone' },
|
||||
{ value: 'push', label: 'Push notification', icon: 'i-heroicons-bell' }
|
||||
]
|
||||
|
||||
const selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URadioGroup v-model="selected" :options="options">
|
||||
<template #label="{ option }">
|
||||
<p class="italic">
|
||||
<UIcon :name="option.icon" />
|
||||
{{ option.label }}
|
||||
</p>
|
||||
</template>
|
||||
</URadioGroup>
|
||||
</template>
|
||||
@@ -3,6 +3,5 @@ const value = ref(50)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label for="range" class="sr-only" />
|
||||
<URange id="range" v-model="value" name="range" />
|
||||
<URange v-model="value" name="range" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" :popper="{ arrow: true }" />
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" :popper="{ offsetDistance: 0 }" />
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" :popper="{ placement: 'left-end' }" />
|
||||
</template>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
const options = [
|
||||
{ id: 1, name: 'Wade Cooper', favoriteColors: ['red', 'yellow'] },
|
||||
{ id: 2, name: 'Arlene Mccoy', favoriteColors: ['blue', 'yellow'] },
|
||||
{ id: 3, name: 'Devon Webb', favoriteColors: ['green', 'blue'] },
|
||||
{ id: 4, name: 'Tom Cook', favoriteColors: ['blue', 'red'] },
|
||||
{ id: 5, name: 'Tanya Fox', favoriteColors: ['green', 'red'] },
|
||||
{ id: 5, name: 'Hellen Schmidt', favoriteColors: ['green', 'yellow'] }
|
||||
]
|
||||
|
||||
const selected = ref(options[1])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
class="w-full lg:w-96"
|
||||
placeholder="Select an user"
|
||||
searchable
|
||||
searchable-placeholder="Search by name or favorite colors"
|
||||
option-attribute="name"
|
||||
by="id"
|
||||
:search-attributes="['name', 'favoriteColors']"
|
||||
>
|
||||
<template #option="{ option: person }">
|
||||
<span v-for="color in person.favoriteColors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
|
||||
<span class="truncate">{{ person.name }}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -84,11 +84,11 @@ 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<{
|
||||
const { data: todos, pending } = await useLazyAsyncData<{
|
||||
id: number
|
||||
title: string
|
||||
completed: string
|
||||
}[]>(`https://jsonplaceholder.typicode.com/todos${searchStatus.value}`, {
|
||||
}[]>('todos', () => ($fetch as any)(`https://jsonplaceholder.typicode.com/todos${searchStatus.value}`, {
|
||||
query: {
|
||||
q: search.value,
|
||||
'_page': page.value,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup>
|
||||
const sort = ref({
|
||||
column: 'name',
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role',
|
||||
sortable: true,
|
||||
direction: 'desc'
|
||||
}]
|
||||
|
||||
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'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model:sort="sort" :columns="columns" :rows="people" />
|
||||
</template>
|
||||
5
docs/components/content/examples/TooltipExampleArrow.vue
Normal file
5
docs/components/content/examples/TooltipExampleArrow.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']" :popper="{ arrow: true }">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']" :popper="{ offsetDistance: 16 }">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']" :popper="{ placement: 'right' }">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
@@ -1,31 +1,20 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Navigation',
|
||||
children: [{
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Data',
|
||||
children: [{
|
||||
label: 'Table',
|
||||
to: '/data/table'
|
||||
}]
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}, {
|
||||
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>
|
||||
<span class="group-hover:text-primary relative">{{ link.label }}</span>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
|
||||
@@ -32,7 +32,10 @@ const links = [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{ wrapper: 'truncate' }"
|
||||
>
|
||||
<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" />
|
||||
|
||||
54
docs/components/releases/ReleasesItem.vue
Normal file
54
docs/components/releases/ReleasesItem.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div ref="target" class="flex flex-col transition-opacity duration-500" :class="targetIsVisible ? 'opacity-100' : 'opacity-25'">
|
||||
<time :datetime="date.day.toISOString()" class="flex-shrink-0 text-sm/6 font-semibold text-gray-500 dark:text-gray-400">{{ date.day.toLocaleString('en-us', { year: 'numeric', month: 'short', day: 'numeric' })
|
||||
}}</time>
|
||||
|
||||
<NuxtLink v-if="date.release" :to="`https://github.com/nuxt/ui/releases/tag/${date.release.name}`" target="_blank" class="text-gray-900 dark:text-white font-bold text-3xl mt-2 group hover:text-primary-500 dark:hover:text-primary-400 transition-[color]">
|
||||
{{ date.release.name }}
|
||||
</NuxtLink>
|
||||
<ul v-else-if="date.pulls?.length" class="mt-2 space-y-1 text-gray-600 dark:text-gray-300">
|
||||
<li v-for="pull in date.pulls" :key="pull.id" class="text-sm/6 break-all">
|
||||
<NuxtLink :to="`https://github.com/${pull.user.login}`" target="_blank" class="text-gray-900 dark:text-white transition-colors inline-flex items-center gap-1 rounded-full bg-gray-100/50 dark:bg-gray-800/50 dark:hover:bg-gray-800 p-0.5 pr-1 ring-1 ring-gray-300 dark:ring-gray-700 text-xs font-medium flex-shrink-0 align-middle">
|
||||
<UAvatar :src="`https://github.com/${pull.user.login}.png`" size="3xs" />
|
||||
|
||||
{{ pull.user.login }}
|
||||
</NuxtLink>
|
||||
|
||||
pushed <NuxtLink :to="pull.html_url" target="_blank" class="font-medium text-gray-700 dark:text-gray-200 hover:text-primary-500 dark:hover:text-primary-400 transition-[color]">
|
||||
#{{ pull.number }} {{ pull.title }}
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useIntersectionObserver } from '@vueuse/core'
|
||||
|
||||
defineProps<{
|
||||
date: {
|
||||
day: Date
|
||||
release?: {
|
||||
name: string
|
||||
}
|
||||
pulls?: {
|
||||
id: number
|
||||
number: number
|
||||
title: string
|
||||
html_url: string
|
||||
user: {
|
||||
login: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}>()
|
||||
|
||||
const target = ref(null)
|
||||
const targetIsVisible = ref(false)
|
||||
|
||||
useIntersectionObserver(target, ([{ isIntersecting }]) => {
|
||||
targetIsVisible.value = isIntersecting
|
||||
}, {
|
||||
rootMargin: '-68px 0px -68px 0px'
|
||||
})
|
||||
</script>
|
||||
27
docs/composables/useContentExamplesCode.ts
Normal file
27
docs/composables/useContentExamplesCode.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
const useContentExamplesCodeState = () => useState('content-examples-code', () => ({}))
|
||||
|
||||
export async function fetchContentExampleCode (name?: string) {
|
||||
if (!name) return
|
||||
const state = useContentExamplesCodeState()
|
||||
|
||||
if (state.value[name]?.then) {
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
if (state.value[name]) { return state.value[name] }
|
||||
|
||||
// add to nitro prerender
|
||||
if (process.server) {
|
||||
const event = useRequestEvent()
|
||||
event.node.res.setHeader(
|
||||
'x-nitro-prerender',
|
||||
[event.node.res.getHeader('x-nitro-prerender'), `/api/content-examples-code/${name}.json`].filter(Boolean).join(',')
|
||||
)
|
||||
}
|
||||
state.value[name] = $fetch(`/api/content-examples-code/${name}.json`).then((data) => {
|
||||
state.value[name] = data
|
||||
})
|
||||
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
@@ -8,7 +8,7 @@ description: 'Learn how to install and configure the module in your Nuxt app.'
|
||||
|
||||
::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@ npm install @nuxt/ui
|
||||
|
||||
2. Add it to your `modules` section in your `nuxt.config`:
|
||||
|
||||
```ts [nuxt.config]
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui']
|
||||
})
|
||||
@@ -150,4 +150,13 @@ Update your `package.json` to the following:
|
||||
}
|
||||
```
|
||||
|
||||
```diff [package.json]
|
||||
{
|
||||
"devDependencies": {
|
||||
- "@nuxt/ui": "^2.9.0"
|
||||
+ "@nuxt/ui": "npm:@nuxt/ui-edge@latest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run `pnpm install`, `yarn install` or `npm install`.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
---
|
||||
description: 'Learn how to customize the look and feel of the components.'
|
||||
navigation:
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Overview
|
||||
@@ -29,6 +27,35 @@ Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i
|
||||
|
||||
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
|
||||
|
||||
When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config.ts`](https://github.com/nuxt/ui/blob/dev/src/runtime/ui.config.ts). You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example.
|
||||
|
||||
```ts [tailwind.config.ts]
|
||||
import type { Config } from 'tailwindcss'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
|
||||
export default <Partial<Config>>{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
green: {
|
||||
50: '#EFFDF5',
|
||||
100: '#D9FBE8',
|
||||
200: '#B3F5D1',
|
||||
300: '#75EDAE',
|
||||
400: '#00DC82',
|
||||
500: '#00C16A',
|
||||
600: '#00A155',
|
||||
700: '#007F45',
|
||||
800: '#016538',
|
||||
900: '#0A5331',
|
||||
950: '#052e16'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CSS Variables
|
||||
|
||||
To provide dynamic colors that can be changed at runtime, this module uses CSS variables. As Tailwind CSS already has a `gray` color, the module automatically renames it to `cool` to avoid conflicts (`coolGray` was renamed to `gray` when Tailwind CSS v3.0 was released).
|
||||
@@ -43,7 +70,7 @@ The `primary` color also has a `DEFAULT` shade that changes based on the theme.
|
||||
|
||||
### Smart Safelisting
|
||||
|
||||
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.
|
||||
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)), [RadioGroup](/forms/radio-group), [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.
|
||||
|
||||
@@ -95,7 +122,7 @@ export default defineAppConfig({
|
||||
|
||||
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `app.config.ts` is smartly merged with the default config. This means you don't have to rewrite everything.
|
||||
|
||||
You can change this behaviour by setting `strategy` to `override` in your `app.config.ts`: :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
You can change this behaviour by setting `strategy` to `override` in your `app.config.ts`:
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
@@ -125,7 +152,7 @@ Each component has a `ui` prop that allows you to customize everything specifica
|
||||
```
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
You can find the default classes for each component under the `Preset` section.
|
||||
You can find the default classes for each component under the `Config` section.
|
||||
::
|
||||
|
||||
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `ui` prop is smartly merged with the config. This means you don't have to rewrite everything.
|
||||
@@ -148,7 +175,7 @@ To change the font of the `label`, you only need to write:
|
||||
|
||||
This will smartly replace the `font-medium` by `font-semibold` and prevent any class duplication and any class priority issue.
|
||||
|
||||
You can change this behaviour by setting `strategy` to `override` inside the `ui` prop: :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
You can change this behaviour by setting `strategy` to `override` inside the `ui` prop:
|
||||
|
||||
```vue
|
||||
<UButton
|
||||
@@ -238,6 +265,8 @@ You can also use the [Icon](/elements/icon) component to add an icon anywhere in
|
||||
</template>
|
||||
```
|
||||
|
||||
### Collections
|
||||
|
||||
By default, the module uses [Heroicons](https://heroicons.com/) but you can change it from the module options in your `nuxt.config.ts`.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
@@ -282,6 +311,42 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
### Custom config :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
If you have specific needs, like using a custom icon collection, you can use the `icons` option in your `nuxt.config.ts` as an object to override the config of the [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons#plugin-options) plugin.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
import { getIconCollections } from '@egoist/tailwindcss-icons'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
icons: {
|
||||
// might solve stretch bug on generate, see https://github.com/egoist/tailwindcss-icons/issues/23
|
||||
extraProperties: {
|
||||
'-webkit-mask-size': 'contain',
|
||||
'-webkit-mask-position': 'center'
|
||||
},
|
||||
collections: {
|
||||
foo: {
|
||||
icons: {
|
||||
'arrow-left': {
|
||||
// svg body
|
||||
body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />',
|
||||
// svg width and height, optional
|
||||
width: 24,
|
||||
height: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
...getIconCollections(['heroicons', 'simple-icons'])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
You can easily replace all the default icons of the components in your `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
@@ -342,13 +407,29 @@ export default defineAppConfig({
|
||||
},
|
||||
pagination: {
|
||||
default: {
|
||||
firstButton: {
|
||||
icon: 'i-octicon-chevron-left-24'
|
||||
},
|
||||
prevButton: {
|
||||
icon: 'i-octicon-arrow-left-24'
|
||||
},
|
||||
nextButton: {
|
||||
icon: 'i-octicon-arrow-right-24'
|
||||
},
|
||||
lastButton: {
|
||||
icon: 'i-octicon-chevron-right-24'
|
||||
}
|
||||
}
|
||||
},
|
||||
accordion: {
|
||||
default: {
|
||||
openIcon: 'i-octicon-chevron-down-24'
|
||||
}
|
||||
},
|
||||
breadcrumb: {
|
||||
default: {
|
||||
divider: 'i-octicon-chevron-right-24'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ description: 'Learn how to display and define keyboard shortcuts in your app.'
|
||||
Some components like [Dropdown](/elements/dropdown), [Command Palette](/navigation/command-palette) and [Tooltip](/overlays/tooltip) support the display of keyboard shortcuts.
|
||||
|
||||
```vue
|
||||
<UDropdown :items="[{ label: 'Edit', shortcuts: ['E'] }]" />
|
||||
<UDropdown :items="[[{ label: 'Edit', shortcuts: ['E'] }]]" />
|
||||
```
|
||||
|
||||
Shortcuts are displayed and styled through the [Kbd](/elements/kbd) component.
|
||||
@@ -62,6 +62,11 @@ Examples of keys:
|
||||
- `shift_e`: will trigger by hitting `Shift` and `E` at the same time on MacOS, Windows and Linux
|
||||
- `?`: will trigger by hitting `?` on some keyboard layouts, or for example `Shift` and `/`, which results in `?` on US Mac keyboards
|
||||
- `g-d`: will trigger by hitting `g` then `d` with a maximum delay of 800ms by default
|
||||
- `arrowleft`: will trigger by hitting `←` (also: `arrowright`, `arrowup`, `arrowdown`)
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
For a complete list of available shortcut keys, refer to the [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) API docs. Note the `KeyboardEvent.key` has to be written in lowercase.
|
||||
::
|
||||
|
||||
### `usingInput`
|
||||
|
||||
@@ -93,9 +98,9 @@ _`enter` shortcut will only trigger when `queryInput` is focused._
|
||||
|
||||
### `whenever`
|
||||
|
||||
Prop: `whenever?: WatchSource<Boolean>[]`
|
||||
Prop: `whenever?: WatchSource<boolean>[]`
|
||||
|
||||
`whenever` allows to add constraints on the shortcut triggering behavior. This array can contain `Ref<Boolean>`, `ComputedRef<Boolean>` or `() => Boolean`.
|
||||
`whenever` allows to add constraints on the shortcut triggering behavior. This array can contain `Ref<boolean>`, `ComputedRef<boolean>` or `() => boolean`.
|
||||
|
||||
```ts
|
||||
defineShortcuts({
|
||||
|
||||
@@ -20,7 +20,7 @@ You can easily build a color mode button by using the `useColorMode` composable
|
||||
:color-mode-button
|
||||
|
||||
#code
|
||||
```vue [components/ColorModeButton.vue]
|
||||
```vue
|
||||
<script setup>
|
||||
const colorMode = useColorMode()
|
||||
|
||||
@@ -109,45 +109,15 @@ const attrs = [{
|
||||
|
||||
You can use it inside a [Popover](/overlays/popover) component to display it when clicking on a [Button](/elements/button).
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:date-picker-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const date = ref(new Date())
|
||||
|
||||
const label = computed(() => date.value.toLocaleDateString('en-us', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' })
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="label" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker v-model="date" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="date-picker-example"}
|
||||
|
||||
### Table
|
||||
|
||||
Here is an example of a Table component with all its features implemented.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
:component-example{component="table-example-advanced" hiddenCode :padding="false" }
|
||||
|
||||
#default
|
||||
:table-example-advanced
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -164,13 +134,14 @@ Here is some examples of what you can do with the [CommandPalette](/navigation/c
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-example-theme-algolia'
|
||||
componentProps:
|
||||
class: 'max-h-[480px] rounded-md'
|
||||
hiddenCode: true
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/CommandPaletteExampleThemeAlgolia.vue#L23" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -179,93 +150,24 @@ Take a look at the component!
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-example-theme-raycast'
|
||||
componentProps:
|
||||
class: 'max-h-[480px] rounded-md'
|
||||
hiddenCode: true
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/CommandPaletteExampleThemeRaycast.vue#L30" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
### VerticalNavigation
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-theme-tailwind
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Introduction',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
to: '/getting-started/theming'
|
||||
}, {
|
||||
label: 'Shortcuts',
|
||||
to: '/getting-started/shortcuts'
|
||||
}, {
|
||||
label: 'Examples',
|
||||
to: '/getting-started/examples'
|
||||
}, {
|
||||
label: 'Roadmap',
|
||||
to: '/getting-started/roadmap'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-s border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-s -ms-px lg:leading-6 before:hidden',
|
||||
padding: 'p-0 ps-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="vertical-navigation-example-theme-tailwind"}
|
||||
|
||||
### Pagination
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-theme-rounded
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination
|
||||
v-model="page"
|
||||
:total="items.length"
|
||||
:ui="{
|
||||
wrapper: 'flex items-center gap-1',
|
||||
rounded: '!rounded-full min-w-[32px] justify-center'
|
||||
}"
|
||||
:prev-button="null"
|
||||
:next-button="{
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid',
|
||||
color: 'primary',
|
||||
variant: 'outline'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="pagination-example-theme-rounded"}
|
||||
|
||||
## RTL Support
|
||||
|
||||
@@ -273,11 +175,8 @@ Here are some examples of how components look like in RTL mode.
|
||||
|
||||
### Pagination
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-example-r-t-l
|
||||
::
|
||||
:component-example{component="pagination-example-r-t-l" hiddenCode}
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/PaginationExampleRTL.vue"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/PaginationExampleRTL.vue" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -9,7 +9,7 @@ Nuxt UI thrives thanks to its fantastic community ❤️, which contributes by s
|
||||
|
||||
Before reporting a bug or reporting a feature, please make sure that you have read through our documentation and existing [issues](https://github.com/nuxt/ui/issues).
|
||||
|
||||
## Submitting a Pull Request
|
||||
## Submitting a Pull Request (PR)
|
||||
|
||||
### 1. Before You Start
|
||||
|
||||
@@ -27,22 +27,33 @@ To begin local development, follow these steps:
|
||||
git clone https://github.com/nuxt/ui.git
|
||||
```
|
||||
|
||||
2. Install dependencies and prepare the project:
|
||||
2. Enable [Corepack](https://github.com/nodejs/corepack):
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
```
|
||||
|
||||
3. Install dependencies:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
4. Generate type stubs:
|
||||
|
||||
```sh
|
||||
pnpm run dev:prepare
|
||||
```
|
||||
|
||||
3. To configure your local development environment, use the following commands:
|
||||
5. Configure your local development environment:
|
||||
|
||||
- To work on the **documentation** in the `docs` folder, run:
|
||||
- To work on the **documentation** located in the `docs` folder, run:
|
||||
|
||||
```sh
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
- To test the components located in the `playground` folder within `app.vue`, run:
|
||||
- To test the components using **playground**, run:
|
||||
|
||||
```sh
|
||||
pnpm run play
|
||||
@@ -84,46 +95,26 @@ pnpm run typecheck
|
||||
|
||||
### 3. Commit Conventions
|
||||
|
||||
Use Conventional Commits for commit messages with the following format:
|
||||
We use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages, which allows a changelog to be auto-generated based on the commits. Please read the [guide](https://www.conventionalcommits.org/en/v1.0.0/#summary) through if you aren't familiar with it already.
|
||||
|
||||
```
|
||||
<type>(optional scope): <description>
|
||||
#### Note
|
||||
|
||||
[optional body]
|
||||
- `fix` and `feat` are for actual code changes (that might affect logic). For typo or document changes, use `docs` or `chore` instead:
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
~~`fix: typo`~~ -> `docs: fix typo`
|
||||
|
||||
#### Types
|
||||
|
||||
- `feat` : for new features.
|
||||
- `fix` : for bug fixes.
|
||||
- `refactor` : for code changes that are neither bug fixes nor new features.
|
||||
- `perf` : for code refactoring that improves performance.
|
||||
- `test` : for code related to automatic testing.
|
||||
- `style` : for refactoring related to code style (not for CSS).
|
||||
- `docs` : for changes related to documentation.
|
||||
- `chore` : for anything else.
|
||||
|
||||
#### Scope
|
||||
|
||||
Where the change occurred (e.g., `Table`, `Alert`, `Accordion`, etc.).
|
||||
|
||||
#### Description
|
||||
|
||||
A summary of the changes made.
|
||||
|
||||
#### Examples
|
||||
- If you are working on a specific component, ensure that you specify the main scope of your commit in brackets. e.g.
|
||||
|
||||
```
|
||||
feat(Alert): new component
|
||||
chore(Table): improve accessibility
|
||||
docs: migrate to @nuxt/ui-pro
|
||||
```
|
||||
|
||||
### 4. Making the Pull Request
|
||||
### 4. Making a Pull Request
|
||||
|
||||
- Follow the guide for creating a pull request and ensure your PR's title adheres to the Commit Convention. Mention any related issues in the PR description.
|
||||
- Follow along the [instructions](https://github.com/nuxt/ui/blob/main/.github/PULL_REQUEST_TEMPLATE.md?plain=1) provided when creating a PR
|
||||
|
||||
- Ensure your PR's title adheres to the [Conventional Commits](https://www.conventionalcommits.org/) since it will be used once the code is merged.
|
||||
|
||||
- Multiple commits are fine; no need to rebase or force push. We'll use `Squash and Merge` when merging.
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Roadmap
|
||||
description: Discover our Volta board for @nuxt/ui development status.
|
||||
toc: false
|
||||
---
|
||||
|
||||
:volta-embed{token="eyJ2aWV3IjoiYm9hcmQiLCJib2FyZFN0YXR1c2VzIjpbInRyaWFnZSIsImJhY2tsb2ciLCJ0b2RvIiwiaW5fcHJvZ3Jlc3MiLCJpbl9yZXZpZXciLCJkb25lIiwicmVsZWFzZWQiLCJjYW5jZWxsZWQiXSwiYm9hcmRMaW5rZWRQcnMiOnRydWUsImxpc3RHcm91cCI6InN0YXR1cyIsImxpc3RPcmRlciI6ImNyZWF0ZWRfYXQiLCJ0aW1lbGluZVpvb20iOiJtb250aCIsInRpbWVsaW5lT3JkZXIiOiJzdGF0ZSIsInRpbWVsaW5lRGlzcGxheSI6ImFsbF9taWxlc3RvbmVzIiwiZmlsdGVycyI6e30sIm93bmVyIjoibnV4dCIsIm5hbWUiOiJ1aSJ9"}
|
||||
@@ -19,31 +19,7 @@ Pass an array to the `items` prop of the Accordion component. Each item can have
|
||||
- `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>
|
||||
```
|
||||
::
|
||||
:component-example{component="accordion-example-basic"}
|
||||
|
||||
### Style
|
||||
|
||||
@@ -65,20 +41,24 @@ 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: ''
|
||||
options:
|
||||
- name: variant
|
||||
restriction: included
|
||||
values:
|
||||
- solid
|
||||
- outline
|
||||
- ghost
|
||||
- soft
|
||||
- link
|
||||
- name: size
|
||||
restriction: included
|
||||
values:
|
||||
- 2xs
|
||||
- xs
|
||||
- sm
|
||||
- md
|
||||
- lg
|
||||
- xl
|
||||
---
|
||||
::
|
||||
|
||||
@@ -165,117 +145,18 @@ You can use slots to customize the buttons and items content of the Accordion.
|
||||
|
||||
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>
|
||||
```
|
||||
::
|
||||
:component-example{component="accordion-example-default-slot"}
|
||||
|
||||
### `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="text-gray-900 dark:text-white text-center">
|
||||
<Logo class="w-auto h-8 mx-auto" />
|
||||
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
||||
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>@nuxt/ui</code> dependency to your project:
|
||||
</p>
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<code>$ npm install @nuxt/ui</code>
|
||||
<code>$ yarn add @nuxt/ui</code>
|
||||
<code>$ pnpm add @nuxt/ui</code>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="accordion-example-item-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
151
docs/content/2.elements/10.progress.md
Normal file
151
docs/content/2.elements/10.progress.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: 'Progress'
|
||||
description: Show a horizontal bar to indicate task progression.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Progress.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Pass an integer as the `value` from `0` to `100` to the Progress bar component.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 70
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Check out the [Range](/forms/range) component for forms.
|
||||
::
|
||||
|
||||
### Max
|
||||
|
||||
You may also set the `max` number to set the maximum progress value, which will be relative to 100% percent.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 2
|
||||
max: 5
|
||||
options:
|
||||
- name: max
|
||||
restriction: only
|
||||
values:
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
---
|
||||
::
|
||||
|
||||
### Steps
|
||||
|
||||
You can set an array of named steps in the `max` prop to show the active step, at the same time it sets the maximum value.
|
||||
|
||||
The first step is always shown at `0%`, making the last `100%`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 0
|
||||
max:
|
||||
- Waiting to start
|
||||
- Cloning...
|
||||
- Migrating...
|
||||
- Deployed!
|
||||
excludedProps:
|
||||
- max
|
||||
---
|
||||
::
|
||||
|
||||
### Progress indicator
|
||||
|
||||
You can add a numeric indicator, which will show the percent on top the progress track.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 20
|
||||
indicator: true
|
||||
---
|
||||
::
|
||||
|
||||
### Indeterminate
|
||||
|
||||
By not setting a `value`, or setting it as `null`, the progress bar becomes _indeterminate_. The bar will be animated as a carousel, but you can change it using the `animation` prop for an inverse carousel, a swinging bar or an elastic bar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: null
|
||||
props:
|
||||
animation: 'carousel'
|
||||
options:
|
||||
- name: animation
|
||||
restriction: only
|
||||
values:
|
||||
- 'carousel'
|
||||
- 'carousel-inverse'
|
||||
- 'swing'
|
||||
- 'elastic'
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the progress bar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 70
|
||||
props:
|
||||
size: 'md'
|
||||
indicator: false
|
||||
excludedProps:
|
||||
- value
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the visual style of the Progress bar. The `color` can be any color from the `ui.colors` object.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 70
|
||||
props:
|
||||
color: 'primary'
|
||||
indicator: false
|
||||
excludedProps:
|
||||
- modelValue
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `indicator`
|
||||
|
||||
You can use the `#indicator` slot to show a custom indicator above the progress bar. It receives the current `percent` of progress.
|
||||
|
||||
:component-example{component="progress-example-slot-indicator"}
|
||||
|
||||
### `step-<index>`
|
||||
|
||||
Use the `#step-<index>` to alter the HTML being shown for each step.
|
||||
|
||||
:component-example{component="progress-example-slot-step"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
178
docs/content/2.elements/11.meter.md
Normal file
178
docs/content/2.elements/11.meter.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: 'Meter'
|
||||
description: Display a gauge meter that fills or depletes.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Meter.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `value` prop from `0` to `100` to set a value for the meter bar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 25
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Check out the [Range](/forms/range) component for inputs
|
||||
::
|
||||
|
||||
### Min & Max
|
||||
|
||||
By default, `min` is `0` and `max` is `100`. You can change either of these using their respective props, even for negative numbers.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: -25
|
||||
min: -50
|
||||
max: 50
|
||||
---
|
||||
::
|
||||
|
||||
### Indicator
|
||||
|
||||
You may show a percentage indicator on top of the meter using the `indicator` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 35
|
||||
indicator: true
|
||||
---
|
||||
::
|
||||
|
||||
### Label
|
||||
|
||||
Add a label below the meter using the `label` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 86
|
||||
props:
|
||||
label: Disk usage
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
You may also add an icon to the start label using the `icon` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 86
|
||||
label: Disk usage
|
||||
props:
|
||||
icon: i-heroicons-server
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Change the size of the meter bar using the `size` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 75.4
|
||||
props:
|
||||
size: 'md'
|
||||
indicator: true
|
||||
label: CPU Load
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
The `color` prop changes the visual style of the meter bar. The `color` can be any color from the `ui.colors` object.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 80
|
||||
indicator: true
|
||||
label: Memory usage
|
||||
props:
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
## Group
|
||||
|
||||
To group multiple meters into a group, adding all values, use the `MeterGroup` component.
|
||||
|
||||
- To change the overall minimum and maximum value, pass the `min` and `max` props respectively.
|
||||
- To change size of all meters, use the `size` prop.
|
||||
- To show an indicator for the overall amount, set the `indicator` prop or slot.
|
||||
- To change the color of each meter, use the `color` prop.
|
||||
- To show a label for each meter, use the `label` prop on each meter.
|
||||
- To change the icon for each meter, use the `icon` prop.
|
||||
|
||||
::component-card{slug="UMeterGroup"}
|
||||
---
|
||||
baseProps:
|
||||
icon: i-heroicons-minus
|
||||
props:
|
||||
min: 0
|
||||
max: 128
|
||||
size: 'md'
|
||||
indicator: true
|
||||
code: |
|
||||
<UMeter :value="24" color="gray" label="System" />
|
||||
<UMeter :value="8" color="red" label="Apps" />
|
||||
<UMeter :value="12" color="yellow" label="Documents" />
|
||||
<UMeter :value="42" color="green" label="Multimedia" />
|
||||
<!-- Total: 86 -->
|
||||
---
|
||||
|
||||
#default
|
||||
:u-meter{:value="24" color="gray" label="System"}
|
||||
:u-meter{:value="8" color="red" label="Apps"}
|
||||
:u-meter{:value="12" color="yellow" label="Documents"}
|
||||
:u-meter{:value="42" color="green" label="Multimedia"}
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
When the Meters are grouped, their individual indicators and label slots are stripped away.
|
||||
::
|
||||
|
||||
A Meter group can also be used with an [indicator slot](#indicator-1), and even individual meter icons.
|
||||
|
||||
:component-example{component="meter-group-example-slots"}
|
||||
|
||||
## Slots
|
||||
|
||||
### `indicator`
|
||||
|
||||
Use the `#indicator` slot to change the indicator shown at the top of the bar. It receives the current meter percent.
|
||||
|
||||
:component-example{component="meter-slot-indicator-example"}
|
||||
|
||||
### `label`
|
||||
|
||||
The `label` slot can be used to change how the label below the meter bar is shown. It receives the current meter percent.
|
||||
|
||||
:component-example{component="meter-slot-label-example"}
|
||||
|
||||
## Props
|
||||
|
||||
::tabs
|
||||
:component-props{label="Meter"}
|
||||
:component-props{label="MeterGroup" slug="UMeterGroup"}
|
||||
::
|
||||
|
||||
## Config
|
||||
|
||||
::tabs
|
||||
:component-preset{label="Meter"}
|
||||
:component-preset{label="MeterGroup" slug="MeterGroup"}
|
||||
::
|
||||
134
docs/content/2.elements/12.chip.md
Normal file
134
docs/content/2.elements/12.chip.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
description: Display a chip indicator on any component.
|
||||
navigation:
|
||||
badge: New
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Chip.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Wrap any component with the `Chip` component to display a chip indicator.
|
||||
|
||||
::component-card
|
||||
---
|
||||
code: >-
|
||||
|
||||
<UButton icon="i-heroicons-inbox" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-button{icon="i-heroicons-inbox" color="gray"}
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the chip.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: '2xl'
|
||||
code: >-
|
||||
|
||||
<UButton icon="i-heroicons-inbox" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-button{icon="i-heroicons-inbox" color="gray"}
|
||||
::
|
||||
|
||||
### Color
|
||||
|
||||
Use the `color` prop to change the color of the chip.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'red'
|
||||
code: >-
|
||||
|
||||
<UButton icon="i-heroicons-inbox" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-button{icon="i-heroicons-inbox" color="gray"}
|
||||
::
|
||||
|
||||
### Position
|
||||
|
||||
Use the `position` prop to change the position of the chip.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
position: 'bottom-right'
|
||||
code: >-
|
||||
|
||||
<UButton icon="i-heroicons-inbox" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-button{icon="i-heroicons-inbox" color="gray"}
|
||||
::
|
||||
|
||||
### Text
|
||||
|
||||
Use the `text` prop to display text in the chip.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
text: '3'
|
||||
size: '2xl'
|
||||
excludedProps:
|
||||
- size
|
||||
code: >-
|
||||
|
||||
<UButton icon="i-heroicons-inbox" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-button{icon="i-heroicons-inbox" color="gray"}
|
||||
::
|
||||
|
||||
### Show
|
||||
|
||||
Use the `show` prop to conditionally display the chip.
|
||||
|
||||
:component-example{component="chip-example-show"}
|
||||
|
||||
### Inset
|
||||
|
||||
Use the `inset` prop to display the chip inside the component. This is useful when dealing with rounded components.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
inset: true
|
||||
code: >-
|
||||
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" alt="Avatar" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" alt="Avatar"}
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `content`
|
||||
|
||||
Use the `#content` slot to fully customize the chip.
|
||||
|
||||
:component-example{component="chip-example-content-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
@@ -79,8 +79,11 @@ props:
|
||||
icon: 'i-heroicons-command-line'
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
extraColors:
|
||||
- white
|
||||
options:
|
||||
- name: color
|
||||
restriction: included
|
||||
values:
|
||||
- white
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -158,30 +161,12 @@ 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>
|
||||
```
|
||||
::
|
||||
:component-example{component="alert-example-html"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -41,8 +41,11 @@ props:
|
||||
chipText: ''
|
||||
chipPosition: 'top-right'
|
||||
size : 'sm'
|
||||
extraColors:
|
||||
- gray
|
||||
options:
|
||||
- name: 'chipColor'
|
||||
restriction: 'included'
|
||||
values:
|
||||
- 'gray'
|
||||
baseProps:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
alt: 'Avatar'
|
||||
@@ -79,7 +82,7 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
## Group
|
||||
|
||||
To stack avatars as a group, use the `AvatarGroup` component.
|
||||
|
||||
@@ -87,22 +90,11 @@ To stack avatars as a group, use the `AvatarGroup` component.
|
||||
- To size all the avatars equally, pass the `size` prop
|
||||
- To adjust the spacing or the ring between avatars, customize with `ui.avatarGroup.margin` or `ui.avatarGroup.ring`
|
||||
|
||||
::component-card{slug="AvatarGroup"}
|
||||
::component-card{slug="UAvatarGroup"}
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
max: 2
|
||||
ui:
|
||||
size:
|
||||
'3xs': ''
|
||||
'2xs': ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
'2xl': ''
|
||||
'3xl': ''
|
||||
code: |
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" alt="benjamincanac" />
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Atinux" />
|
||||
@@ -117,8 +109,14 @@ code: |
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
::tabs
|
||||
:component-props{label="Avatar"}
|
||||
:component-props{label="AvatarGroup" slug="UAvatarGroup"}
|
||||
::
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
::tabs
|
||||
:component-preset{label="Avatar"}
|
||||
:component-preset{label="AvatarGroup" slug="AvatarGroup"}
|
||||
::
|
||||
|
||||
@@ -38,6 +38,7 @@ Use the `color` and `variant` props to change the visual style of the Badge.
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -52,11 +53,14 @@ Besides all the colors from the `ui.colors` object, you can also use the `white`
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
excludedProps:
|
||||
- color
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -69,11 +73,14 @@ Badge
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
excludedProps:
|
||||
- color
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -86,12 +93,14 @@ Badge
|
||||
props:
|
||||
color: 'black'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
link: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: only
|
||||
values:
|
||||
- solid
|
||||
excludedProps:
|
||||
- color
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -105,6 +114,7 @@ Use the `size` prop to change the size of the Badge.
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -121,6 +131,7 @@ props:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- ui
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -134,6 +145,6 @@ You can customize the whole [preset](#preset) by using the `ui` prop.
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -36,6 +36,7 @@ Use the `color` and `variant` props to change the visual style of the Button.
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -51,12 +52,15 @@ backgroundClass: 'bg-gray-50 dark:bg-gray-800'
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
ghost: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
- ghost
|
||||
excludedProps:
|
||||
- color
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -69,13 +73,16 @@ Button
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
ghost: 1
|
||||
link: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
- ghost
|
||||
- link
|
||||
excludedProps:
|
||||
- color
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -88,12 +95,15 @@ Button
|
||||
props:
|
||||
color: 'black'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
link: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
- link
|
||||
excludedProps:
|
||||
- color
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -107,6 +117,7 @@ Use the `size` prop to change the size of the Button.
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -123,6 +134,7 @@ props:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- ui
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -177,6 +189,7 @@ Use the `disabled` prop to disable the Button.
|
||||
---
|
||||
props:
|
||||
disabled: true
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -192,6 +205,7 @@ Use the `loading-icon` prop to set a different icon or change it globally in `ui
|
||||
---
|
||||
props:
|
||||
loading: true
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -205,6 +219,7 @@ Use the `block` prop to make the Button fill the width of its container.
|
||||
---
|
||||
props:
|
||||
block: true
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -219,6 +234,7 @@ Use the `to` prop to make the Button a link.
|
||||
props:
|
||||
to: 'https://volta.net'
|
||||
target: '_blank'
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -271,7 +287,7 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
## Group
|
||||
|
||||
To stack buttons as a group, use the `ButtonGroup` component.
|
||||
|
||||
@@ -279,22 +295,11 @@ To stack buttons as a group, use the `ButtonGroup` component.
|
||||
- To change the orientation of the buttons, set the `orientation` prop to `vertical`
|
||||
- To adjust the rounded or the shadow around buttons, customize with `ui.buttonGroup.rounded` or `ui.buttonGroup.shadow`
|
||||
|
||||
::component-card{slug="ButtonGroup"}
|
||||
::component-card{slug="UButtonGroup"}
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
orientation: 'horizontal'
|
||||
ui:
|
||||
size:
|
||||
2xs: ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
orientation:
|
||||
horizontal: ''
|
||||
vertical: ''
|
||||
code: |
|
||||
<UButton label="Action" color="white" />
|
||||
<UButton icon="i-heroicons-chevron-down-20-solid" color="gray" />
|
||||
@@ -305,6 +310,23 @@ code: |
|
||||
:u-button{icon="i-heroicons-chevron-down-20-solid" color="gray"}
|
||||
::
|
||||
|
||||
This can also work with an [Input](/forms/input) component for example:
|
||||
|
||||
::component-card{slug="UButtonGroup"}
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
orientation: 'horizontal'
|
||||
code: |
|
||||
<UInput />
|
||||
<UButton icon="i-heroicons-clipboard-document" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input
|
||||
:u-button{icon="i-heroicons-clipboard-document" color="gray"}
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `leading`
|
||||
@@ -349,8 +371,14 @@ excludedProps:
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
::tabs
|
||||
:component-props{label="Button"}
|
||||
:component-props{label="ButtonGroup" slug="UButtonGroup"}
|
||||
::
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
::tabs
|
||||
:component-preset{label="Button"}
|
||||
:component-preset{label="ButtonGroup" slug="ButtonGroup"}
|
||||
::
|
||||
|
||||
@@ -24,81 +24,29 @@ Pass an array of arrays to the `items` prop of the Dropdown component. Each arra
|
||||
|
||||
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
|
||||
:dropdown-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E'],
|
||||
click: () => {
|
||||
console.log('Edit')
|
||||
}
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D'],
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
}, {
|
||||
label: 'Move',
|
||||
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||
}], [{
|
||||
label: 'Delete',
|
||||
icon: 'i-heroicons-trash-20-solid',
|
||||
shortcuts: ['⌘', 'D']
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ placement: 'bottom-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="dropdown-example-basic"}
|
||||
|
||||
### Mode
|
||||
|
||||
Use the `mode` prop to switch between `click` and `hover` modes.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example-mode
|
||||
:component-example{component="dropdown-example-mode"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
## Popper
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" mode="hover" :popper="{ placement: 'bottom-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
Use the `popper` prop to customize the popper instance.
|
||||
|
||||
### Arrow
|
||||
|
||||
:component-example{component="dropdown-example-arrow"}
|
||||
|
||||
### Placement
|
||||
|
||||
:component-example{component="dropdown-example-placement"}
|
||||
|
||||
### Offset
|
||||
|
||||
:component-example{component="dropdown-example-offset"}
|
||||
|
||||
## Slots
|
||||
|
||||
@@ -106,66 +54,12 @@ const items = [
|
||||
|
||||
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>
|
||||
```
|
||||
::
|
||||
:component-example{component="dropdown-example-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Display an icon from Iconify library.
|
||||
description: Display any icon (100,000+) from Iconify.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
@@ -8,6 +8,8 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `name` prop by following this pattern: `i-{collection_name}-{icon_name}`. You can search any icon from [Iconify](https://iconify.design/) using https://icones.js.org.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
@@ -16,9 +18,35 @@ props:
|
||||
::
|
||||
|
||||
::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).
|
||||
You won't be able to use any icon in the `name` prop here as icons are bundled using [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons), read more about this in [Theming](/getting-started/theming#icons).
|
||||
::
|
||||
|
||||
### Dynamic :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
You can use the `dynamic` prop to enable dynamic icon loading. This will use [`nuxt-icon`](https://github.com/nuxt-modules/icon) instead and allow you to use any icon from [Iconify](https://iconify.design/) as well as the `{collection_name}:{icon_name}` pattern.
|
||||
|
||||
This can be quite useful when using [dynamic class names](https://tailwindcss.com/docs/content-configuration#dynamic-class-names) or for icons that are not bundled by default (fetched from a database for example).
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
name: 'i-ph-rocket-launch'
|
||||
dynamic: true
|
||||
---
|
||||
::
|
||||
|
||||
You can also change the default behavior of the `dynamic` prop by setting the `ui.icons.dynamic` option in your `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
icons: {
|
||||
dynamic: true
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -32,24 +32,7 @@ props:
|
||||
|
||||
As explained in the [Shortcuts](/getting-started/shortcuts) page, you can use the `metaSymbol` property of the `useShortcuts` composable to display the meta key according to the user's OS.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:kbd-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="kbd-example"}
|
||||
|
||||
### Size
|
||||
|
||||
@@ -69,6 +52,6 @@ U
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -17,6 +17,18 @@ The Link component is a wrapper around [`<NuxtLink>`](https://nuxt.com/docs/api/
|
||||
|
||||
The incentive behind this is to provide the same API as NuxtLink back in Nuxt 2 / Vue 2. You can read more about it in the Vue Router [migration from Vue 2](https://router.vuejs.org/guide/migration/#removal-of-the-exact-prop-in-router-link) guide.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
to: /elements/link
|
||||
activeClass: 'text-primary'
|
||||
inactiveClass: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'
|
||||
code: ' Link '
|
||||
---
|
||||
|
||||
Link
|
||||
::
|
||||
|
||||
It also renders an `<a>` tag when a `to` prop is provided, otherwise it defaults to rendering a `<button>` tag. The default behavior can be customized using the `as` prop.
|
||||
|
||||
It is used underneath by the [Button](/elements/button), [Dropdown](/elements/dropdown) and [VerticalNavigation](/navigation/vertical-navigation) components.
|
||||
|
||||
@@ -10,21 +10,7 @@ links:
|
||||
|
||||
Use a `v-model` to make the Input reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:input-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput v-model="value" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="input-example"}
|
||||
|
||||
### Style
|
||||
|
||||
@@ -33,7 +19,6 @@ Use the `color` and `variant` props to change the visual style of the Input.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'primary'
|
||||
@@ -48,7 +33,6 @@ Besides all the colors from the `ui.colors` object, you can also use the `white`
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'white'
|
||||
@@ -63,7 +47,6 @@ excludedProps:
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'gray'
|
||||
@@ -79,21 +62,30 @@ Use the `size` prop to change the size of the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
Use the `type` prop to change the input type, the default `type` is set to `text`, you can check all the available types at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types).
|
||||
|
||||
We have improved the implementation of certain types such as [Checkbox](/forms/checkbox), [Radio](/forms/radio-group), etc.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
type: 'password'
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
Use the `placeholder` prop to set a placeholder text.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
@@ -108,16 +100,18 @@ Use the `leading` and `trailing` props to set the icon position or the `leading-
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
size: 'sm'
|
||||
color: 'white'
|
||||
trailing: false
|
||||
extraColors:
|
||||
- white
|
||||
- gray
|
||||
options:
|
||||
- name: color
|
||||
restriction: included
|
||||
values:
|
||||
- white
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -130,7 +124,6 @@ Use the `disabled` prop to disable the Input.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
disabled: true
|
||||
@@ -146,7 +139,6 @@ Use the `loading-icon` prop to set a different icon or change it globally in `ui
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Searching...'
|
||||
props:
|
||||
loading: true
|
||||
@@ -167,7 +159,6 @@ Use the `#leading` slot to set the content of the leading icon.
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
@@ -184,7 +175,6 @@ Use the `#trailing` slot to set the content of the trailing icon.
|
||||
slots:
|
||||
trailing: <span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
@@ -194,32 +184,7 @@ baseProps:
|
||||
|
||||
You can for example create a clearable Input by injecting a [Button](/elements/button) in the `trailing` slot that displays when some text is entered.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:input-example-clearable
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UInput v-model="q" name="q" placeholder="Search..." icon="i-heroicons-magnifying-glass-20-solid" :ui="{ icon: { trailing: { pointer: '' } } }">
|
||||
<template #trailing>
|
||||
<UButton
|
||||
v-show="q !== ''"
|
||||
color="gray"
|
||||
variant="link"
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
:padded="false"
|
||||
@click="q = ''"
|
||||
/>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const q = ref('')
|
||||
</script>
|
||||
```
|
||||
::
|
||||
:component-example{component="input-example-clearable"}
|
||||
|
||||
::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.
|
||||
@@ -229,6 +194,6 @@ As leading and trailing icons are wrapped around a `pointer-events-none` class,
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -18,53 +18,11 @@ The Form component requires the `validate` and `state` props for form validation
|
||||
- `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 { ref } from 'vue'
|
||||
import type { FormError, FormSubmitEvent } from '@nuxt/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
|
||||
}
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:validate="validate"
|
||||
:state="state"
|
||||
@submit="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>
|
||||
```
|
||||
---
|
||||
component: 'form-example-basic'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
## Schema
|
||||
@@ -74,215 +32,41 @@ You can provide a schema from [Yup](#yup), [Zod](#zod) or [Joi](#joi), [Valibot]
|
||||
### Yup
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-yup{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { object, string, InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = object({
|
||||
email: string().email('Invalid email').required('Required'),
|
||||
password: string()
|
||||
.min(8, 'Must be at least 8 characters')
|
||||
.required('Required')
|
||||
})
|
||||
|
||||
type Schema = InferType<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="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>
|
||||
```
|
||||
---
|
||||
component: 'form-example-yup'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Zod
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-zod{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/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')
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="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>
|
||||
```
|
||||
---
|
||||
component: 'form-example-zod'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Joi
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-joi{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Joi from 'joi'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string()
|
||||
.min(8)
|
||||
.required()
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="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>
|
||||
```
|
||||
---
|
||||
component: 'form-example-joi'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Valibot
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-valibot{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { string, object, email, minLength, Input } from 'valibot'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = object({
|
||||
email: string([email('Invalid email')]),
|
||||
password: string([minLength(8, 'Must be at least 8 characters')])
|
||||
})
|
||||
|
||||
type Schema = Input<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="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>
|
||||
```
|
||||
---
|
||||
component: 'form-example-valibot'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
## Other libraries
|
||||
@@ -332,32 +116,32 @@ You can manually set errors after form submission if required. To do this, simpl
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { FormError, FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormError, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
form.value.clear()
|
||||
const response = await fetch('...')
|
||||
|
||||
if (!response.status === 422) {
|
||||
const errors = await response.json()
|
||||
form.value.setErrors(errors.map((err) => {
|
||||
// Map validation errors to { path: string, message: string }
|
||||
}))
|
||||
} else {
|
||||
try {
|
||||
const response = await $fetch('...')
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err.statusCode === 422) {
|
||||
form.value.setErrors(err.data.errors.map((err) => {
|
||||
// Map validation errors to { path: string, message: string }
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :state="state" @submit="submit">
|
||||
<UForm ref="form" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
@@ -377,9 +161,39 @@ async function submit (event: FormSubmitEvent<any>) {
|
||||
|
||||
The Form component automatically triggers validation upon `submit`, `input`, `blur` or `change` events. This ensures that any errors are displayed as soon as the user interacts with the form elements. You can control when validation happens this using the `validate-on` prop.
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Note that the `input` event is not triggered until after the initial `blur` event. This is to prevent the form from being validated as the user is typing. You can override this behavior by setting the [`eager-validation`](/forms/form-group#eager-validation) prop on [`FormGroup`](/forms/form-group) to `true`.
|
||||
::
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-elements{class="space-y-4 w-60"}
|
||||
---
|
||||
component: 'form-example-elements'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
hiddenCode: true
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/FormExampleElements.vue" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
## Error event
|
||||
|
||||
You can listen to the `@error` event to handle errors. This event is triggered when the form is validated and contains an array of `FormError` objects with the following fields:
|
||||
|
||||
- `id` - the identifier of the form element.
|
||||
- `path` - the path to the form element matching the `name`.
|
||||
- `message` - the error message to display.
|
||||
|
||||
Here is an example of how to focus the first form element with an error:
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-on-error'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user