mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10a9a3ea2b | ||
|
|
1ff11ac1a3 | ||
|
|
07b27a228d | ||
|
|
6be9290f68 | ||
|
|
0f3fe0d54e | ||
|
|
0815f688ed | ||
|
|
8399ffe1f1 | ||
|
|
91f6103719 | ||
|
|
278a1ea93c | ||
|
|
3f27c0ccae | ||
|
|
5a2f46683a | ||
|
|
c8a0005253 | ||
|
|
881f3547f2 | ||
|
|
8c99b871e2 | ||
|
|
41b85d50a8 | ||
|
|
759af058df | ||
|
|
48636363d1 | ||
|
|
4ea114a4d6 | ||
|
|
ad2349e570 | ||
|
|
ffb312d34d | ||
|
|
97a1c86433 | ||
|
|
c2ebb0416e | ||
|
|
e1548062c7 | ||
|
|
9cd73aa49d | ||
|
|
a880379480 | ||
|
|
71c2465d7b | ||
|
|
0272307f28 | ||
|
|
2ea358703e | ||
|
|
c458f388bb | ||
|
|
1b03b8a531 | ||
|
|
e2f7d82d62 | ||
|
|
c8e6ed8df9 | ||
|
|
a67f691a00 | ||
|
|
dfccbcf1a9 | ||
|
|
38ecb088ec | ||
|
|
8236b18d0d | ||
|
|
1e05b0f072 | ||
|
|
87e98f038a | ||
|
|
f7e2082983 | ||
|
|
f719111abb | ||
|
|
3bac0874f1 | ||
|
|
457b7a9fb7 | ||
|
|
4023fbec29 | ||
|
|
a274a0cdbb | ||
|
|
717a514451 | ||
|
|
786d7765f5 | ||
|
|
a733c13866 | ||
|
|
88c1930845 | ||
|
|
c3f5c44461 | ||
|
|
2cfa1f8d03 | ||
|
|
5f7de8e595 | ||
|
|
cdce519742 | ||
|
|
ccd9ca5106 | ||
|
|
9031742acc | ||
|
|
9559d0b3bc | ||
|
|
0e6550ec45 | ||
|
|
20fa4d2317 | ||
|
|
e12e9740c9 | ||
|
|
cbc8ef13cc | ||
|
|
652af93f5c | ||
|
|
b4a96a8b01 | ||
|
|
bc81d45b2b | ||
|
|
429791dab0 | ||
|
|
fe833eb2b2 | ||
|
|
be5f352296 | ||
|
|
47415322ea | ||
|
|
d20983d355 | ||
|
|
f0b24ba25d | ||
|
|
f7a34c8fee | ||
|
|
4e5e614eb4 | ||
|
|
07f7855a26 | ||
|
|
57f95102e2 | ||
|
|
3f8d927438 | ||
|
|
d91c0bb894 | ||
|
|
a6176720c7 | ||
|
|
a6903df58f | ||
|
|
19b149518e | ||
|
|
c66a99a60f | ||
|
|
4a7c6035b6 | ||
|
|
207444fdea | ||
|
|
60eea0e46b | ||
|
|
5e50eb9eb8 | ||
|
|
af65683123 | ||
|
|
2c673f5377 | ||
|
|
192b0e6301 | ||
|
|
71edb91c4f | ||
|
|
f9b935f5f5 | ||
|
|
23833e92cb | ||
|
|
241df7f05e | ||
|
|
130a1f2c54 | ||
|
|
c63981e31c | ||
|
|
687f0c6f63 | ||
|
|
f59a92ca15 | ||
|
|
01fa85c7a3 | ||
|
|
3434bc7f2b | ||
|
|
9b1aacb1da | ||
|
|
8951923a11 | ||
|
|
e200d4cc74 | ||
|
|
e05619f8c8 | ||
|
|
5ea43ab4e4 | ||
|
|
ba44c58a80 | ||
|
|
490025a981 | ||
|
|
2966373a86 | ||
|
|
8bdb8c45f7 | ||
|
|
9827de0b58 | ||
|
|
23f01fde41 | ||
|
|
f680318e44 | ||
|
|
cd2d1eb1fa | ||
|
|
3ba0aedcba | ||
|
|
40b6884424 | ||
|
|
a2638c6057 | ||
|
|
6bd5142a37 | ||
|
|
bc1d653857 | ||
|
|
6c215e07a6 | ||
|
|
272af9d24c | ||
|
|
cce000ab2b | ||
|
|
4a99d6a7bb | ||
|
|
4458656be5 | ||
|
|
daca46371c | ||
|
|
8ee2ac10e7 | ||
|
|
1ebaa5aa00 | ||
|
|
cb43548305 | ||
|
|
360084af7c |
@@ -2,6 +2,9 @@ module.exports = {
|
||||
root: true,
|
||||
extends: ['@nuxt/eslint-config'],
|
||||
rules: {
|
||||
'semi': ['error', 'never'],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
'space-before-function-paren': ['error', 'always'],
|
||||
'vue/multi-word-component-names': 0,
|
||||
'vue/max-attributes-per-line': ['error', {
|
||||
singleline: {
|
||||
|
||||
2
.github/workflows/ci-dev.yml
vendored
2
.github/workflows/ci-dev.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 7
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 7
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ dist
|
||||
.DS_Store
|
||||
.history
|
||||
.vercel
|
||||
.idea
|
||||
|
||||
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
23
.release-it.json
Normal file
23
.release-it.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "chore(release): ${version}"
|
||||
},
|
||||
"npm": {
|
||||
"publish": false
|
||||
},
|
||||
"github": {
|
||||
"release": true,
|
||||
"web": true
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": ["pnpm lint"]
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": "conventionalcommits",
|
||||
"infile": "CHANGELOG.md",
|
||||
"header": "# Changelog",
|
||||
"ignoreRecommendedBump": true
|
||||
}
|
||||
}
|
||||
}
|
||||
121
CHANGELOG.md
121
CHANGELOG.md
@@ -1,6 +1,123 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
## [2.5.0](https://github.com/nuxtlabs/ui/compare/v2.4.1...v2.5.0) (2023-06-27)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **Radio/Checkbox/Toggle:** handle `color` prop for form elements (#323)
|
||||
|
||||
### Features
|
||||
|
||||
* **Avatar:** handle `chipText` ([#306](https://github.com/nuxtlabs/ui/issues/306)) ([759af05](https://github.com/nuxtlabs/ui/commit/759af058df636f55a54326b21ebb1c315c73c26b))
|
||||
* **defineShortcuts:** chained shortcuts + docs update ([#282](https://github.com/nuxtlabs/ui/issues/282)) ([a67f691](https://github.com/nuxtlabs/ui/commit/a67f691a0066e4d017f580388df31b22d1c45372))
|
||||
* **Radio/Checkbox/Toggle:** handle `color` prop for form elements ([#323](https://github.com/nuxtlabs/ui/issues/323)) ([ffb312d](https://github.com/nuxtlabs/ui/commit/ffb312d34dfc2ac7a7aabdcbdf9ddb1d04d8a66f))
|
||||
* **Range:** new component ([#290](https://github.com/nuxtlabs/ui/issues/290)) ([97a1c86](https://github.com/nuxtlabs/ui/commit/97a1c8643314d5ff950b122f46f31b206485cd50))
|
||||
* RTL support ([#320](https://github.com/nuxtlabs/ui/issues/320)) ([4ea114a](https://github.com/nuxtlabs/ui/commit/4ea114a4d6b11277674c121130f746927045ade3))
|
||||
* **Table:** pass row index to table cell ([#291](https://github.com/nuxtlabs/ui/issues/291)) ([71c2465](https://github.com/nuxtlabs/ui/commit/71c2465d7be78cfb0e274b107aceda9de5384fb7))
|
||||
* **Table:** reset sort on third click ([1ff11ac](https://github.com/nuxtlabs/ui/commit/1ff11ac1a3eff537a4ee854a049668f312f1d415)), closes [#300](https://github.com/nuxtlabs/ui/issues/300)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **components:** prefix `@headlessui/vue` components ([41b85d5](https://github.com/nuxtlabs/ui/commit/41b85d50a865cfe4aa0f06a62f5209358422eaec)), closes [#315](https://github.com/nuxtlabs/ui/issues/315)
|
||||
* **defineShortcuts:** missing `ref` import ([a880379](https://github.com/nuxtlabs/ui/commit/a8803794802c4032f703a0a0a6343a8204b19bc8))
|
||||
* **defineShortcuts:** missing `useDebounceFn` import ([9cd73aa](https://github.com/nuxtlabs/ui/commit/9cd73aa49d1dd43bac8ec71932b850bdcb375fcf))
|
||||
* **FormGroup:** prevent overriding `color` of children ([6be9290](https://github.com/nuxtlabs/ui/commit/6be9290f689c449b6a6435a3ef25e89a106e1c06)), closes [#352](https://github.com/nuxtlabs/ui/issues/352)
|
||||
* **Table:** default `sortButton` icon ([07b27a2](https://github.com/nuxtlabs/ui/commit/07b27a228d293655368825979a6ca0bc1dd6e51a))
|
||||
* **Table:** missing default sort icon when overriding `sort-button` prop ([0f3fe0d](https://github.com/nuxtlabs/ui/commit/0f3fe0d54ef8b45a046b84ceb31ae55a26e153fb))
|
||||
* **Toggle:** add `opacity-50` when disabled ([c2ebb04](https://github.com/nuxtlabs/ui/commit/c2ebb0416eb2c92b759be5a4bf0d219031889b4b))
|
||||
* **Tooltip:** add `color` in config ([1b03b8a](https://github.com/nuxtlabs/ui/commit/1b03b8a531d397871e0df4f8574d7f47ac4ec610))
|
||||
|
||||
### [2.4.1](https://github.com/nuxtlabs/ui/compare/v2.4.0...v2.4.1) (2023-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **forms:** precise type assertion for `onInput` event handler ([#293](https://github.com/nuxtlabs/ui/issues/293)) ([457b7a9](https://github.com/nuxtlabs/ui/commit/457b7a9fb72e6469014b6ca18e7034dd5c6f44b8))
|
||||
* **module:** let `tailwindcss` viewer enabled by default ([4023fbe](https://github.com/nuxtlabs/ui/commit/4023fbec29e5b4d40fd23e8c2ae3d0cf23addc64)), closes [#292](https://github.com/nuxtlabs/ui/issues/292)
|
||||
* **module:** safelist aliases for input ([f719111](https://github.com/nuxtlabs/ui/commit/f719111abb94c81f3932927a0154b3e1bed73a9a))
|
||||
* **module:** safelist regex when a `:` was present before color ([f7e2082](https://github.com/nuxtlabs/ui/commit/f7e2082983c2eb650e95a9040aafde4ce2c88c54))
|
||||
* **Radio/Checkbox:** remove legacy `custom` ([3bac087](https://github.com/nuxtlabs/ui/commit/3bac0874f106a8ff7436b541f9d064c1c7c27464))
|
||||
|
||||
|
||||
## [2.4.0](https://github.com/nuxtlabs/ui/compare/v2.3.0...v2.4.0) (2023-06-13)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **forms:** bind `$attrs` to elements (#279)
|
||||
* **Select:** rename `text-attribute` to `option-attribute` and defaults to `label`
|
||||
|
||||
### Features
|
||||
|
||||
* **CommandPalette:** handle `empty-state` ([#271](https://github.com/nuxtlabs/ui/issues/271)) ([652af93](https://github.com/nuxtlabs/ui/commit/652af93f5c7cd4b34044a5597f3c14441ed6d998))
|
||||
* **module:** smart safelisting ([#268](https://github.com/nuxtlabs/ui/issues/268)) ([20fa4d2](https://github.com/nuxtlabs/ui/commit/20fa4d2317fc1e14fe87fa273957b92e63668945))
|
||||
* **Pagination:** new component ([#257](https://github.com/nuxtlabs/ui/issues/257)) ([f0b24ba](https://github.com/nuxtlabs/ui/commit/f0b24ba25d52184b8683e364016ed8fb800fc96b))
|
||||
* **table:** add loading state ([#259](https://github.com/nuxtlabs/ui/issues/259)) ([4741532](https://github.com/nuxtlabs/ui/commit/47415322ea56b5388e55c404c901531e807a9f00))
|
||||
* **table:** add slot for empty state ([#260](https://github.com/nuxtlabs/ui/issues/260)) ([f7a34c8](https://github.com/nuxtlabs/ui/commit/f7a34c8feeda6a4e1e1daff87b37b375aaa0c90d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ButtonGroup:** invalid `size` validator ([a617672](https://github.com/nuxtlabs/ui/commit/a6176720c75b26768ba91efcab50689a932931ad))
|
||||
* **ButtonGroup:** use `-space-x-px` on wrapper ([d91c0bb](https://github.com/nuxtlabs/ui/commit/d91c0bb8944224d4e8eb62f99a33a6be94e5cd92))
|
||||
* **Button:** same size when no label + uniformize form elements ([a6903df](https://github.com/nuxtlabs/ui/commit/a6903df58fb91da44e6f83cc2bd9c963827fe5dd))
|
||||
* **CommandPalette:** input focus after be5f352 ([cbc8ef1](https://github.com/nuxtlabs/ui/commit/cbc8ef13cc3253690c22c32d90ea9746970c345a))
|
||||
* **deps:** move `@tailwindcss/container-queries` to dependencies ([9559d0b](https://github.com/nuxtlabs/ui/commit/9559d0b3bc09956d7fe17ee0deeef03599d02d45))
|
||||
* **forms:** `padded` prop with `p-0` class ([207444f](https://github.com/nuxtlabs/ui/commit/207444fdea773b8ee64dd4f80b4f70b76462a9d6))
|
||||
* **forms:** bind `$attrs` to elements ([#279](https://github.com/nuxtlabs/ui/issues/279)) ([e12e974](https://github.com/nuxtlabs/ui/commit/e12e9740c97b75d3b7b70c38978e249b5e26eead))
|
||||
* **module:** deduplicate default safelist as components may share same rules ([2cfa1f8](https://github.com/nuxtlabs/ui/commit/2cfa1f8d0355d4c9cec5d4294d63e043d223cd64))
|
||||
* **module:** hardcode `gray` safelist instead of deduplicate complex logic ([a733c13](https://github.com/nuxtlabs/ui/commit/a733c13866cdb74398f3e6f022cc63223e269e19))
|
||||
* **module:** only safelist known colors ([cdce519](https://github.com/nuxtlabs/ui/commit/cdce519742b86ff29460aa50264d7bb34ad24bd0))
|
||||
* **module:** prevent safelisting dynamic `:color` variables ([ccd9ca5](https://github.com/nuxtlabs/ui/commit/ccd9ca5106d0b81aed6591097f121eb81dcc9b47))
|
||||
* **module:** transform `vue` files to detect multi-line components ([88c1930](https://github.com/nuxtlabs/ui/commit/88c1930845d26c66c2fbd32f99f52dbd23244341))
|
||||
* **module:** use `@tailwindcss/forms` class strategy ([#278](https://github.com/nuxtlabs/ui/issues/278)) ([be5f352](https://github.com/nuxtlabs/ui/commit/be5f352296cf4e0c9099cf468ed905283b31007d))
|
||||
* **Notification:** class priority for icon color ([07f7855](https://github.com/nuxtlabs/ui/commit/07f7855a263e516250f62d0730afc69753d0322c))
|
||||
* **Radio/Checkbox:** split preset as `indeterminate` is checkbox only ([429791d](https://github.com/nuxtlabs/ui/commit/429791dab0fbb84bae1e1e13e7e688708f0b5c98))
|
||||
* **SelectMenu:** input focus after `be5f352` ([717a514](https://github.com/nuxtlabs/ui/commit/717a5144511c4db013a57869ac06421accf51e38))
|
||||
* **Table:** colspan of `empty` and `loading` is wrong when selection enabled ([#284](https://github.com/nuxtlabs/ui/issues/284)) ([786d776](https://github.com/nuxtlabs/ui/commit/786d7765f5517a7e8cdd718ce93fd9fecc427ba7))
|
||||
* **Toggle:** missing `disabled` prop ([fe833eb](https://github.com/nuxtlabs/ui/commit/fe833eb2b2b4d1d32eb9e082b437a0259b6f75c6))
|
||||
|
||||
|
||||
* **Select:** rename `text-attribute` to `option-attribute` and defaults to `label` ([b4a96a8](https://github.com/nuxtlabs/ui/commit/b4a96a8b01b52751c9a9c6609ed8cf7ccf516a04))
|
||||
|
||||
## [2.3.0](https://github.com/nuxtlabs/ui/compare/v2.2.1...v2.3.0) (2023-06-05)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **Input:** move pointer class inside its own preset class
|
||||
* **SelectMenu:** remove `inline-flex` from wrapper to behave like other form elements
|
||||
* **Notification:** rename to `closeButton` and `actionButton` for consistency
|
||||
* **CommandPalette:** rename props to `emptyState` and `closeButton` for consistency
|
||||
* **Toggle:** rename icons to `onIcon` / `offIcon` for consistency
|
||||
|
||||
### Features
|
||||
|
||||
* add `Table` component ([#237](https://github.com/nuxtlabs/ui/issues/237)) ([cce000a](https://github.com/nuxtlabs/ui/commit/cce000ab2b2af1079216e0e79769703fc4d9933e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Avatar:** placeholder font size ([71edb91](https://github.com/nuxtlabs/ui/commit/71edb91c4ff17a258d6229ed6c6fa6a4b54bdd53))
|
||||
* **Badge:** remove `console.log` in validator ([f9b935f](https://github.com/nuxtlabs/ui/commit/f9b935f5f59b872fd952a2739d305d6574bf7cf8))
|
||||
* **Button:** invalid padding when using `square` prop ([1ebaa5a](https://github.com/nuxtlabs/ui/commit/1ebaa5aa00752cd276f7c754d64ac7f85b14dc26))
|
||||
* **CommandPalette:** override of `closeButton` and `emptyState` props ([2c673f5](https://github.com/nuxtlabs/ui/commit/2c673f5377dbbcdefa6b57eddba2c19d065d5f1f))
|
||||
* **defineShortcuts:** err with input autocomplete that triggers `keydown` ([01fa85c](https://github.com/nuxtlabs/ui/commit/01fa85c7a3e476d4f710ed3a36c1e815fc986a94))
|
||||
* **SelectMenu:** disable on loading ([8951923](https://github.com/nuxtlabs/ui/commit/8951923a11d533ebf53dbec5f852800555af253c))
|
||||
* **Table:** add missing `text-left` in `th.base` ([6bd5142](https://github.com/nuxtlabs/ui/commit/6bd5142a377694599952e0f9b53fde0d0132b61b))
|
||||
* **Table:** missing `ref` import from `vue` ([272af9d](https://github.com/nuxtlabs/ui/commit/272af9d24c7cda8341e66b57f76acdb9f46ea23e))
|
||||
* **Table:** override of `sortButton` and `emptyState` props ([192b0e6](https://github.com/nuxtlabs/ui/commit/192b0e63018ae73e8acaa8b4b1771cda2b59bdb6))
|
||||
* **Table:** type `sort` prop ([3ba0aed](https://github.com/nuxtlabs/ui/commit/3ba0aedcba578350e2fdd9c180505ed8920e0404))
|
||||
* use `cloneVNode` when altering props in render functions ([5e50eb9](https://github.com/nuxtlabs/ui/commit/5e50eb9eb82571d22e0a2f1a2fe985addf7efe18)), closes [#252](https://github.com/nuxtlabs/ui/issues/252)
|
||||
|
||||
|
||||
* **CommandPalette:** rename props to `emptyState` and `closeButton` for consistency ([daca463](https://github.com/nuxtlabs/ui/commit/daca46371cab1344bd87ffb0abe0f7e9cdb08609))
|
||||
* **Input:** move pointer class inside its own preset class ([f59a92c](https://github.com/nuxtlabs/ui/commit/f59a92ca1533a44e17fbc8b7945bdaa9a83e805a))
|
||||
* **Notification:** rename to `closeButton` and `actionButton` for consistency ([4458656](https://github.com/nuxtlabs/ui/commit/4458656be5547fc9505a5c4758bea4818ada408b))
|
||||
* **SelectMenu:** remove `inline-flex` from wrapper to behave like other form elements ([ba44c58](https://github.com/nuxtlabs/ui/commit/ba44c58a80252a4394fcf2f84611ea2696883120))
|
||||
* **Toggle:** rename icons to `onIcon` / `offIcon` for consistency ([8ee2ac1](https://github.com/nuxtlabs/ui/commit/8ee2ac10e7eda4c54418f613a5ef87dd89e1f7eb))
|
||||
|
||||
### [2.2.1](https://github.com/nuxtlabs/ui/compare/v2.2.0...v2.2.1) (2023-05-27)
|
||||
|
||||
@@ -541,4 +658,4 @@ All notable changes to this project will be documented in this file. See [standa
|
||||
* **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))
|
||||
@@ -14,6 +14,7 @@ This module has been developed by [NuxtLabs](https://nuxtlabs.com/) for [Volta](
|
||||
- Built with [Headless UI](https://headlessui.dev/) and [Tailwind CSS](https://tailwindcss.com/)
|
||||
- HMR support through Nuxt App Config
|
||||
- Dark mode support
|
||||
- Support for LTR and RTL languages
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
|
||||
29
docs/app.vue
29
docs/app.vue
@@ -2,25 +2,11 @@
|
||||
<div>
|
||||
<Header />
|
||||
|
||||
<UContainer>
|
||||
<div class="relative grid lg:grid-cols-10 lg:gap-8">
|
||||
<DocsAside class="lg:col-span-2" />
|
||||
<UContainer class="grid lg:grid-cols-10 lg:gap-8">
|
||||
<DocsAside class="lg:col-span-2" />
|
||||
|
||||
<div class="relative pt-8 pb-16" :class="[toc ? 'lg:col-span-6' : 'lg:col-span-8']">
|
||||
<DocsPageHeader />
|
||||
|
||||
<NuxtPage />
|
||||
|
||||
<DocsPageFooter class="mt-12" />
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-6">
|
||||
|
||||
<DocsPrevNext />
|
||||
|
||||
<DocsFooter class="mt-16" />
|
||||
</div>
|
||||
|
||||
<DocsToc v-if="toc" class="lg:col-span-2 order-first lg:order-last" />
|
||||
<div class="lg:col-span-8 min-h-0 flex flex-col">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</UContainer>
|
||||
|
||||
@@ -33,9 +19,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { toc } = useContent()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation())
|
||||
|
||||
provide('navigation', navigation)
|
||||
|
||||
// Computed
|
||||
|
||||
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
|
||||
@@ -56,7 +45,7 @@ useHead({
|
||||
lang: 'en'
|
||||
},
|
||||
bodyAttrs: {
|
||||
class: 'antialiased font-sans text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-900'
|
||||
class: 'antialiased font-sans text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
46
docs/components/ColorPicker.vue
Normal file
46
docs/components/ColorPicker.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerButton v-for="color in primaryColors" :key="color.value" :color="color" :selected="primary" @select="primary = color" />
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-2">
|
||||
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerButton v-for="color in grayColors" :key="color.value" :color="color" :selected="gray" @select="gray = color" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryColors = computed(() => useWithout(appConfig.ui.colors, 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get () {
|
||||
return primaryColors.value.find(option => option.value === appConfig.ui.primary)
|
||||
},
|
||||
set (option) {
|
||||
appConfig.ui.primary = option.value
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.primary)
|
||||
}
|
||||
})
|
||||
|
||||
const grayColors = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const gray = computed({
|
||||
get () {
|
||||
return grayColors.value.find(option => option.value === appConfig.ui.gray)
|
||||
},
|
||||
set (option) {
|
||||
appConfig.ui.gray = option.value
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-gray', appConfig.ui.gray)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
25
docs/components/ColorPickerButton.vue
Normal file
25
docs/components/ColorPickerButton.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<UTooltip :text="color.value" class="capitalize" :open-delay="500">
|
||||
<UButton
|
||||
color="gray"
|
||||
square
|
||||
:ui="{
|
||||
color: {
|
||||
gray: {
|
||||
solid: 'bg-gray-100 dark:bg-gray-800',
|
||||
ghost: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'
|
||||
}
|
||||
}
|
||||
}"
|
||||
:variant="color.value === selected.value ? 'solid' : 'ghost'"
|
||||
@click.stop.prevent="$emit('select')"
|
||||
>
|
||||
<span class="inline-block w-3 h-3 rounded-full" :style="{ backgroundColor: color.hex }" />
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ color: { value: string, hex: string }, selected: { value: string} }>()
|
||||
defineEmits(['select'])
|
||||
</script>
|
||||
76
docs/components/DatePicker.vue
Normal file
76
docs/components/DatePicker.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
|
||||
import 'v-calendar/dist/style.css'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:model-value', 'close'])
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed(() => colorMode.value === 'dark')
|
||||
|
||||
const date = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:model-value', value)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = [{
|
||||
key: 'today',
|
||||
highlight: {
|
||||
color: 'blue',
|
||||
fillMode: 'outline',
|
||||
class: '!bg-gray-100 dark:!bg-gray-800'
|
||||
},
|
||||
dates: new Date()
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCalendarDatePicker
|
||||
v-model="date"
|
||||
transparent
|
||||
borderless
|
||||
:attributes="attrs"
|
||||
:is-dark="isDark"
|
||||
title-position="left"
|
||||
trim-weeks
|
||||
:first-day-of-week="2"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--vc-gray-50: rgb(var(--color-gray-50));
|
||||
--vc-gray-100: rgb(var(--color-gray-100));
|
||||
--vc-gray-200: rgb(var(--color-gray-200));
|
||||
--vc-gray-300: rgb(var(--color-gray-300));
|
||||
--vc-gray-400: rgb(var(--color-gray-400));
|
||||
--vc-gray-500: rgb(var(--color-gray-500));
|
||||
--vc-gray-600: rgb(var(--color-gray-600));
|
||||
--vc-gray-700: rgb(var(--color-gray-700));
|
||||
--vc-gray-800: rgb(var(--color-gray-800));
|
||||
--vc-gray-900: rgb(var(--color-gray-900));
|
||||
}
|
||||
|
||||
.vc-blue {
|
||||
--vc-accent-50: rgb(var(--color-primary-50));
|
||||
--vc-accent-100: rgb(var(--color-primary-100));
|
||||
--vc-accent-200: rgb(var(--color-primary-200));
|
||||
--vc-accent-300: rgb(var(--color-primary-300));
|
||||
--vc-accent-400: rgb(var(--color-primary-400));
|
||||
--vc-accent-500: rgb(var(--color-primary-500));
|
||||
--vc-accent-600: rgb(var(--color-primary-600));
|
||||
--vc-accent-700: rgb(var(--color-primary-700));
|
||||
--vc-accent-800: rgb(var(--color-primary-800));
|
||||
--vc-accent-900: rgb(var(--color-primary-900));
|
||||
}
|
||||
</style>
|
||||
@@ -1,86 +1,16 @@
|
||||
<template>
|
||||
<header class="sticky top-0 z-50 w-full backdrop-blur flex-none border-b border-gray-900/10 dark:border-gray-50/[0.06] bg-white/75 dark:bg-gray-900/75">
|
||||
<header class="sticky top-0 z-50 w-full backdrop-blur flex-none border-b border-gray-200 dark:border-gray-800 bg-white/75 dark:bg-gray-900/75">
|
||||
<UContainer>
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
NuxtLabs<span class="text-primary-500 dark:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center -mr-1.5">
|
||||
<div class="mr-1.5 hidden lg:block">
|
||||
<ThemeSelect />
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
@click="openDocsSearch"
|
||||
/>
|
||||
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon' : 'i-heroicons-sun'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<UButton
|
||||
to="https://github.com/nuxtlabs/ui"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-github"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
icon="i-heroicons-bars-3-20-solid"
|
||||
@click="isDialogOpen = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HeaderLinks v-model="isDialogOpen" :links="links" />
|
||||
</UContainer>
|
||||
|
||||
<TransitionRoot :show="isDialogOpen" as="template">
|
||||
<Dialog as="div" @close="isDialogOpen = false">
|
||||
<DialogPanel class="fixed inset-0 z-50 overflow-y-auto bg-white dark:bg-gray-900 lg:hidden">
|
||||
<div class="px-4 sm:px-6 sticky top-0 border-b border-gray-900/10 dark:border-gray-50/[0.06] bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
NuxtLabs<span class="text-primary-500 dark:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="flex -mr-1.5">
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
@click="isDialogOpen = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 sticky top-0 border-b border-gray-200 dark:border-gray-800 bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">
|
||||
<HeaderLinks v-model="isDialogOpen" :links="links" />
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 py-4 sm:py-6">
|
||||
<ThemeSelect class="mb-4 sm:mb-6 w-full" />
|
||||
|
||||
<DocsAsideLinks @click="isDialogOpen = false" />
|
||||
</div>
|
||||
</DialogPanel>
|
||||
@@ -92,25 +22,11 @@
|
||||
<script setup lang="ts">
|
||||
import { Dialog, DialogPanel, TransitionRoot } from '@headlessui/vue'
|
||||
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDialogOpen = ref(false)
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
|
||||
function openDocsSearch () {
|
||||
isDialogOpen.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
isSearchModalOpen.value = true
|
||||
}, 100)
|
||||
}
|
||||
const links = [
|
||||
{ label: 'Documentation', to: '/getting-started' },
|
||||
{ label: 'Components', to: '/elements/avatar' },
|
||||
{ label: 'Examples', to: '/examples' }
|
||||
]
|
||||
</script>
|
||||
|
||||
64
docs/components/HeaderLinks.vue
Normal file
64
docs/components/HeaderLinks.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between gap-3 h-16">
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
<span class="hidden sm:block">NuxtLabs</span><span class="sm:text-primary-500 dark:sm:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end flex-1 -mr-1.5 gap-3">
|
||||
<DocsSearchButton class="ml-1.5 flex-1 lg:flex-none lg:w-48" />
|
||||
|
||||
<div class="flex items-center lg:gap-1.5">
|
||||
<UPopover>
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<ColorPicker />
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<ColorModeButton />
|
||||
|
||||
<UButton
|
||||
to="https://twitter.com/nuxtlabs"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-twitter"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
to="https://github.com/nuxtlabs/ui"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-github"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
:icon="isDialogOpen ? 'i-heroicons-x-mark-20-solid' : 'i-heroicons-bars-3-20-solid'"
|
||||
@click="isDialogOpen = !isDialogOpen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ modelValue: boolean, links: { to: string, label: string }[] }>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const isDialogOpen = useVModel(props, 'modelValue', emit)
|
||||
</script>
|
||||
@@ -1,130 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center shadow-sm">
|
||||
<ClientOnly>
|
||||
<USelectMenu
|
||||
v-model="primary"
|
||||
name="primary"
|
||||
class="w-full [&>div>button]:!rounded-r-none"
|
||||
color="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-start' }"
|
||||
:options="primaryOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${primary.hex}`}" />
|
||||
|
||||
{{ primary.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</ClientOnly>
|
||||
|
||||
<ClientOnly>
|
||||
<USelectMenu
|
||||
v-model="gray"
|
||||
name="gray"
|
||||
class="w-full [&>div>button]:!rounded-l-none [&>div>button]:-ml-px"
|
||||
color="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-end' }"
|
||||
:options="grayOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${gray.hex}`}" />
|
||||
|
||||
{{ gray.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const primaryCookie = useCookie('primary', { path: '/', default: () => appConfig.ui.primary })
|
||||
const grayCookie = useCookie('gray', { path: '/', default: () => appConfig.ui.gray })
|
||||
|
||||
watch(primaryCookie, (primary) => {
|
||||
appConfig.ui.primary = primary
|
||||
}, { immediate: true })
|
||||
|
||||
watch(grayCookie, (gray) => {
|
||||
appConfig.ui.gray = gray
|
||||
}, { immediate: true })
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryOptions = computed(() => useWithout(appConfig.ui.colors, 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get () {
|
||||
return primaryOptions.value.find(option => option.value === primaryCookie.value)
|
||||
},
|
||||
set (option) {
|
||||
primaryCookie.value = option.value
|
||||
}
|
||||
})
|
||||
|
||||
const grayOptions = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const gray = computed({
|
||||
get () {
|
||||
return grayOptions.value.find(option => option.value === grayCookie.value)
|
||||
},
|
||||
set (option) {
|
||||
grayCookie.value = option.value
|
||||
}
|
||||
})
|
||||
|
||||
// Hack for SSG
|
||||
const hexToRgb = (hex) => {
|
||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
|
||||
hex = hex.replace(shorthandRegex, function (_, r, g, b) {
|
||||
return r + r + g + g + b + b
|
||||
})
|
||||
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}`
|
||||
: null
|
||||
}
|
||||
const root = computed(() => {
|
||||
return `:root {
|
||||
${Object.entries(colors[primary.value.value] || colors.green).map(([key, value]) => `--color-primary-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
${Object.entries(colors[gray.value.value] || colors.cool).map(([key, value]) => `--color-gray-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
}`
|
||||
})
|
||||
if (process.client) {
|
||||
watch(root, () => {
|
||||
window.localStorage.setItem('nuxt-ui-root', root.value)
|
||||
}, { immediate: true })
|
||||
}
|
||||
if (process.server) {
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
innerHTML: `
|
||||
if (localStorage.getItem('nuxt-ui-root')) {
|
||||
document.querySelector('style#nuxt-ui-colors').innerHTML = localStorage.getItem('nuxt-ui-root')
|
||||
}`.replace(/\s+/g, ' '),
|
||||
type: 'text/javascript',
|
||||
tagPriority: -1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<component
|
||||
:is="to ? NuxtLink : 'div'"
|
||||
:to="to"
|
||||
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm leading-6 my-5 last:mb-0 font-normal group relative"
|
||||
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm leading-6 my-5 last:mb-0 font-normal group relative prose-code:bg-white dark:prose-code:bg-gray-900"
|
||||
:class="[to ? 'hover:!border-primary-500 dark:hover:!border-primary-400 hover:text-primary-500 dark:hover:text-primary-400 border-dashed hover:text-gray-800 dark:hover:text-gray-200' : '']"
|
||||
>
|
||||
<UIcon v-if="!!to" name="i-heroicons-link-20-solid" class="w-3 h-3 absolute right-2 top-2 text-gray-400 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
|
||||
28
docs/components/content/ColorModeButton.vue
Normal file
28
docs/components/content/ColorModeButton.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -2,46 +2,52 @@
|
||||
<div>
|
||||
<div v-if="propsToSelect.length" class="relative flex border border-gray-200 dark:border-gray-700 rounded-t-md overflow-hidden not-prose">
|
||||
<div v-for="prop in propsToSelect" :key="prop.name" class="flex flex-col gap-0.5 justify-between py-1.5 font-medium bg-gray-50 dark:bg-gray-800 border-r border-r-gray-200 dark:border-r-gray-700">
|
||||
<label :for="prop.name" class="block text-xs px-3 font-medium text-gray-400 dark:text-gray-500 -my-px">{{ prop.label }}</label>
|
||||
<label :for="`prop-${prop.name}`" class="block text-xs px-3 font-medium text-gray-400 dark:text-gray-500 -my-px">{{ prop.label }}</label>
|
||||
<UCheckbox
|
||||
v-if="prop.type === 'boolean'"
|
||||
v-model="componentProps[prop.name]"
|
||||
:name="prop.name"
|
||||
variant="none"
|
||||
class="justify-center"
|
||||
:name="`prop-${prop.name}`"
|
||||
tabindex="-1"
|
||||
:ui="{ wrapper: 'relative flex items-start justify-center' }"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-else-if="prop.type === 'string' && prop.options.length"
|
||||
v-model="componentProps[prop.name]"
|
||||
:options="prop.options"
|
||||
:name="prop.name"
|
||||
:label="componentProps[prop.name]"
|
||||
:name="`prop-${prop.name}`"
|
||||
variant="none"
|
||||
class="inline-flex"
|
||||
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md' }"
|
||||
:ui-select="{ custom: '!py-0' }"
|
||||
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md', wrapper: 'relative inline-flex' }"
|
||||
class="!py-0"
|
||||
tabindex="-1"
|
||||
:popper="{ strategy: 'fixed', placement: 'bottom-start' }"
|
||||
/>
|
||||
<UInput
|
||||
v-else
|
||||
:model-value="componentProps[prop.name]"
|
||||
:type="prop.type === 'number' ? 'number' : 'text'"
|
||||
:name="prop.name"
|
||||
:name="`prop-${prop.name}`"
|
||||
variant="none"
|
||||
autocomplete="off"
|
||||
:ui="{ custom: '!py-0' }"
|
||||
class="!py-0"
|
||||
tabindex="-1"
|
||||
@update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex border border-b-0 border-gray-200 dark:border-gray-700 relative not-prose" :class="[{ 'p-4': padding }, propsToSelect.length ? 'border-t-0' : 'rounded-t-md', backgroundClass]">
|
||||
<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">
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<ClientOnly>
|
||||
<ContentSlot v-if="$slots[slot]" :use="$slots[slot]" />
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
|
||||
<ContentRenderer :value="ast" class="[&>div>pre]:!rounded-t-none" />
|
||||
<ContentRenderer v-if="!previewOnly" :value="ast" class="[&>div>pre]:!rounded-t-none" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -67,6 +73,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
slots: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
baseProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
@@ -86,6 +96,14 @@ const props = defineProps({
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-white dark:bg-gray-900'
|
||||
},
|
||||
overflowClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
previewOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
@@ -107,7 +125,7 @@ const meta = await fetchComponentMeta(name)
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const ui = computed(() => ({ ...appConfig.ui[camelName], ...props.ui }))
|
||||
|
||||
const fullProps = computed(() => ({ ...props.baseProps, ...componentProps }))
|
||||
const fullProps = computed(() => ({ ...baseProps, ...componentProps }))
|
||||
const vModel = computed({
|
||||
get: () => baseProps.modelValue,
|
||||
set: (value) => {
|
||||
@@ -150,7 +168,14 @@ const code = computed(() => {
|
||||
|
||||
code += ` ${(prop?.type === 'boolean' && value !== true) || typeof value === 'object' ? ':' : ''}${key === 'modelValue' ? 'value' : useKebabCase(key)}${prop?.type === 'boolean' && !!value && key !== 'modelValue' ? '' : `="${typeof value === 'object' ? renderObject(value) : value}"`}`
|
||||
}
|
||||
if (props.code) {
|
||||
|
||||
if (props.slots) {
|
||||
code += `>
|
||||
${Object.entries(props.slots).map(([key, value]) => `<template #${key}>
|
||||
${value}
|
||||
</template>`).join('\n ')}
|
||||
</${name}>`
|
||||
} else if (props.code) {
|
||||
const lineBreaks = (props.code.match(/\n/g) || []).length
|
||||
if (lineBreaks > 1) {
|
||||
code += `>
|
||||
@@ -183,7 +208,7 @@ function renderObject (obj: any) {
|
||||
return obj
|
||||
}
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify(componentProps)}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify(props)}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="[&>div>pre]:!rounded-t-none">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 relative not-prose rounded-t-md" :class="[{ 'p-4': padding, 'rounded-b-md': !$slots.code, 'border-b-0': !!$slots.code }, backgroundClass]">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 relative not-prose rounded-t-md" :class="[{ 'p-4': padding, 'rounded-b-md': !$slots.code, 'border-b-0': !!$slots.code }, backgroundClass, overflowClass]">
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,10 @@ defineProps({
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-white dark:bg-gray-900'
|
||||
},
|
||||
overflowClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
7
docs/components/content/examples/CheckboxExample.vue
Normal file
7
docs/components/content/examples/CheckboxExample.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
const selected = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCheckbox v-model="selected" name="notifications" label="Notifications" />
|
||||
</template>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<UCommandPalette>
|
||||
<template #empty-state>
|
||||
<div class="flex flex-col items-center justify-center py-6 gap-3">
|
||||
<span class="italic text-sm">Nothing here!</span>
|
||||
<UButton label="Add item" />
|
||||
</div>
|
||||
</template>
|
||||
</UCommandPalette>
|
||||
</template>
|
||||
16
docs/components/content/examples/DatePickerExample.vue
Normal file
16
docs/components/content/examples/DatePickerExample.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<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>
|
||||
16
docs/components/content/examples/DropdownExampleMode.vue
Normal file
16
docs/components/content/examples/DropdownExampleMode.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" mode="hover" :popper="{ placement: 'bottom-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
7
docs/components/content/examples/InputExample.vue
Normal file
7
docs/components/content/examples/InputExample.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput v-model="value" />
|
||||
</template>
|
||||
18
docs/components/content/examples/InputExampleClearable.vue
Normal file
18
docs/components/content/examples/InputExampleClearable.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<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>
|
||||
@@ -0,0 +1,8 @@
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination v-model="page" :page-count="5" :total="items.length" />
|
||||
</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 #prev="{ onClick }">
|
||||
<UTooltip text="Previous page">
|
||||
<UButton icon="i-heroicons-arrow-small-left-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:first-child]:rotate-180 me-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<template #next="{ onClick }">
|
||||
<UTooltip text="Next page">
|
||||
<UButton icon="i-heroicons-arrow-small-right-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:last-child]:rotate-180 ms-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
</UPagination>
|
||||
</template>
|
||||
23
docs/components/content/examples/RadioExample.vue
Normal file
23
docs/components/content/examples/RadioExample.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<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 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" />
|
||||
</div>
|
||||
</template>
|
||||
7
docs/components/content/examples/RangeExample.vue
Normal file
7
docs/components/content/examples/RangeExample.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
const value = ref(50)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URange v-model="value" />
|
||||
</template>
|
||||
9
docs/components/content/examples/SelectExample.vue
Normal file
9
docs/components/content/examples/SelectExample.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const countries = ['United States', 'Canada', 'Mexico']
|
||||
|
||||
const country = ref(countries[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model="country" :options="countries" />
|
||||
</template>
|
||||
18
docs/components/content/examples/SelectExampleObjects.vue
Normal file
18
docs/components/content/examples/SelectExampleObjects.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
const countries = [{
|
||||
name: 'United States',
|
||||
value: 'US'
|
||||
}, {
|
||||
name: 'Canada',
|
||||
value: 'CA'
|
||||
}, {
|
||||
name: 'Mexico',
|
||||
value: 'MX'
|
||||
}]
|
||||
|
||||
const country = ref('CA')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model="country" :options="countries" option-attribute="name" />
|
||||
</template>
|
||||
@@ -6,10 +6,10 @@ const selected = ref(people[3])
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton>
|
||||
<UButton color="gray">
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
|
||||
@@ -5,10 +5,5 @@ const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple>
|
||||
<template #label>
|
||||
<span v-if="selected.length" class="font-medium truncate">{{ selected.join(', ') }}</span>
|
||||
<span v-else class="block truncate text-gray-400 dark:text-gray-500">Select people</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<USelectMenu v-model="selected" :options="people" multiple placeholder="Select people" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple>
|
||||
<template #label>
|
||||
<span v-if="selected.length" class="truncate">{{ selected.join(', ') }}</span>
|
||||
<span v-else>Select people</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
43
docs/components/content/examples/TableExampleBasic.vue
Normal file
43
docs/components/content/examples/TableExampleBasic.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" />
|
||||
</template>
|
||||
59
docs/components/content/examples/TableExampleColumns.vue
Normal file
59
docs/components/content/examples/TableExampleColumns.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'User name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Job position'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role'
|
||||
}]
|
||||
|
||||
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 :columns="columns" :rows="people" />
|
||||
</template>
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}]
|
||||
|
||||
const selectedColumns = ref([...columns])
|
||||
|
||||
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>
|
||||
<div>
|
||||
<div class="flex px-3 py-3.5 border-b border-gray-200 dark:border-gray-700">
|
||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple placeholder="Columns" />
|
||||
</div>
|
||||
|
||||
<UTable :columns="selectedColumns" :rows="people" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
sortable: true,
|
||||
direction: 'desc'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}]
|
||||
|
||||
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 :columns="columns" :rows="people" :sort="{ column: 'title' }" />
|
||||
</template>
|
||||
30
docs/components/content/examples/TableExampleEmptySlot.vue
Normal file
30
docs/components/content/examples/TableExampleEmptySlot.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}, {
|
||||
key: 'actions'
|
||||
}]
|
||||
|
||||
const people = []
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" :columns="columns">
|
||||
<template #empty-state>
|
||||
<div class="flex flex-col items-center justify-center py-6 gap-3">
|
||||
<span class="italic text-sm">No one here!</span>
|
||||
<UButton label="Add people" />
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
86
docs/components/content/examples/TableExampleLoadingSlot.vue
Normal file
86
docs/components/content/examples/TableExampleLoadingSlot.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}, {
|
||||
key: 'actions'
|
||||
}]
|
||||
|
||||
const people = []
|
||||
|
||||
const pending = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" :columns="columns" :loading="pending">
|
||||
<template #loading-state>
|
||||
<div class="flex items-center justify-center h-32">
|
||||
<i class="loader --6" />
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* https://codepen.io/jenning/pen/YzNmzaV */
|
||||
|
||||
.loader {
|
||||
--color: rgb(var(--color-primary-400));
|
||||
--size-mid: 6vmin;
|
||||
--size-dot: 1.5vmin;
|
||||
--size-bar: 0.4vmin;
|
||||
--size-square: 3vmin;
|
||||
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.loader::before,
|
||||
.loader::after {
|
||||
content: '';
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/**
|
||||
loader --6
|
||||
**/
|
||||
.loader.--6::before {
|
||||
width: var(--size-square);
|
||||
height: var(--size-square);
|
||||
background-color: var(--color);
|
||||
top: calc(50% - var(--size-square));
|
||||
left: calc(50% - var(--size-square));
|
||||
animation: loader-6 2.4s cubic-bezier(0, 0, 0.24, 1.21) infinite;
|
||||
}
|
||||
|
||||
@keyframes loader-6 {
|
||||
0%, 100% {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(100%) translateY(100%);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
docs/components/content/examples/TableExamplePaginable.vue
Normal file
98
docs/components/content/examples/TableExamplePaginable.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 7,
|
||||
name: 'Emily Selman',
|
||||
title: 'VP, User Experience',
|
||||
email: '',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 8,
|
||||
name: 'Kristin Watson',
|
||||
title: 'VP, Human Resources',
|
||||
email: '',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 9,
|
||||
name: 'Emma Watson',
|
||||
title: 'Front-end Developer',
|
||||
email: '',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 10,
|
||||
name: 'John Doe',
|
||||
title: 'Designer',
|
||||
email: '',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 11,
|
||||
name: 'Jane Doe',
|
||||
title: 'Director of Product',
|
||||
email: '',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 12,
|
||||
name: 'John Smith',
|
||||
title: 'Copywriter',
|
||||
email: '',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 13,
|
||||
name: 'Jane Smith',
|
||||
title: 'Senior Designer',
|
||||
email: '',
|
||||
role: 'Owner'
|
||||
}]
|
||||
|
||||
const page = ref(1)
|
||||
const pageCount = 5
|
||||
|
||||
const rows = computed(() => {
|
||||
return people.slice((page.value - 1) * pageCount, (page.value) * pageCount)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UTable :rows="rows" />
|
||||
|
||||
<div class="flex justify-end px-3 py-3.5 border-t border-gray-200 dark:border-gray-700">
|
||||
<UPagination v-model="page" :page-count="pageCount" :total="people.length" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
80
docs/components/content/examples/TableExampleSearchable.vue
Normal file
80
docs/components/content/examples/TableExampleSearchable.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}]
|
||||
|
||||
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'
|
||||
}]
|
||||
|
||||
const q = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
if (!q.value) {
|
||||
return people
|
||||
}
|
||||
|
||||
return people.filter((person) => {
|
||||
return Object.values(person).some((value) => {
|
||||
return String(value).toLowerCase().includes(q.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex px-3 py-3.5 border-b border-gray-200 dark:border-gray-700">
|
||||
<UInput v-model="q" placeholder="Filter people..." />
|
||||
</div>
|
||||
|
||||
<UTable :rows="filteredRows" :columns="columns" />
|
||||
</div>
|
||||
</template>
|
||||
45
docs/components/content/examples/TableExampleSelectable.vue
Normal file
45
docs/components/content/examples/TableExampleSelectable.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}]
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" />
|
||||
</template>
|
||||
91
docs/components/content/examples/TableExampleSlots.vue
Normal file
91
docs/components/content/examples/TableExampleSlots.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}, {
|
||||
key: 'actions'
|
||||
}]
|
||||
|
||||
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'
|
||||
}]
|
||||
|
||||
const items = (row) => [
|
||||
[{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
click: () => console.log('Edit', row.id)
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid'
|
||||
}], [{
|
||||
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'
|
||||
}]
|
||||
]
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" :columns="columns">
|
||||
<template #name-data="{ row }">
|
||||
<span :class="[selected.find(person => person.id === row.id) && 'text-primary-500 dark:text-primary-400']">{{ row.name }}</span>
|
||||
</template>
|
||||
|
||||
<template #actions-data="{ row }">
|
||||
<UDropdown :items="items(row)">
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-ellipsis-horizontal-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
7
docs/components/content/examples/TextareaExample.vue
Normal file
7
docs/components/content/examples/TextareaExample.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTextarea v-model="value" />
|
||||
</template>
|
||||
7
docs/components/content/examples/ToggleExample.vue
Normal file
7
docs/components/content/examples/ToggleExample.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
const selected = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UToggle v-model="selected" />
|
||||
</template>
|
||||
15
docs/components/content/prose/ProseH4.vue
Normal file
15
docs/components/content/prose/ProseH4.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ id: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3 :id="id" class="scroll-mt-[145px] lg:scroll-mt-[96px]">
|
||||
<NuxtLink :href="`#${id}`" class="group">
|
||||
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
|
||||
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</h3>
|
||||
</template>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const { navigation } = useContent()
|
||||
const navigation = inject('navigation')
|
||||
|
||||
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
|
||||
|
||||
@@ -17,8 +17,8 @@ const groups = computed(() => navigation.value.map(item => ({
|
||||
}))
|
||||
})))
|
||||
|
||||
const close = computed(() => commandPaletteRef.value?.query ? ({ icon: 'i-heroicons-x-mark', color: 'black', variant: 'ghost', size: 'lg', padded: false }) : null)
|
||||
const empty = computed(() => commandPaletteRef.value?.query ? ({ icon: 'i-heroicons-magnifying-glass', queryLabel: 'No results' }) : ({ icon: '', label: 'No recent searches' }))
|
||||
const closeButton = computed(() => commandPaletteRef.value?.query ? ({ icon: 'i-heroicons-x-mark', color: 'black', variant: 'ghost', size: 'lg', padded: false }) : null)
|
||||
const emptyState = computed(() => commandPaletteRef.value?.query ? ({ icon: 'i-heroicons-magnifying-glass', queryLabel: 'No results' }) : ({ icon: '', label: 'No recent searches' }))
|
||||
|
||||
const ui = {
|
||||
wrapper: 'flex flex-col flex-1 min-h-0 bg-gray-50 dark:bg-gray-800',
|
||||
@@ -50,7 +50,7 @@ const ui = {
|
||||
}
|
||||
}
|
||||
},
|
||||
empty: {
|
||||
emptyState: {
|
||||
wrapper: 'flex flex-col items-center justify-center flex-1 py-9',
|
||||
label: 'text-sm text-center text-gray-500 dark:text-gray-400',
|
||||
queryLabel: 'text-lg text-center text-gray-900 dark:text-white',
|
||||
@@ -64,8 +64,8 @@ const ui = {
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
:ui="ui"
|
||||
:close="close"
|
||||
:empty="empty"
|
||||
:close-button="closeButton"
|
||||
:empty-state="emptyState"
|
||||
:autoselect="false"
|
||||
command-attribute="title"
|
||||
:fuse="{
|
||||
|
||||
@@ -31,7 +31,7 @@ const ui = {
|
||||
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-200 dark:divide-gray-700 bg-gray-50 dark:bg-gray-800',
|
||||
container: 'relative flex-1 overflow-y-auto divide-y divide-gray-200 dark:divide-gray-700 scroll-py-2',
|
||||
input: {
|
||||
base: 'w-full h-14 px-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0'
|
||||
base: 'w-full h-14 px-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0 focus:outline-none'
|
||||
},
|
||||
group: {
|
||||
label: 'px-2 my-2 text-xs font-semibold text-gray-500 dark:text-gray-400',
|
||||
|
||||
68
docs/components/content/themes/LTRAndRTLTheme.vue
Normal file
68
docs/components/content/themes/LTRAndRTLTheme.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full gap-3">
|
||||
<div class="flex justify-between w-full mb-2 border-b pb-4">
|
||||
<div dir="ltr">
|
||||
<UInput
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
size="sm"
|
||||
color="white"
|
||||
placeholder="Search..."
|
||||
:trailing="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div dir="rtl">
|
||||
<UInput
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
size="sm"
|
||||
color="white"
|
||||
placeholder="ابحث..."
|
||||
:trailing="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between w-full mt-4">
|
||||
<div dir="ltr">
|
||||
<UPagination
|
||||
v-model="page"
|
||||
:total="items.length"
|
||||
:prev-button="{
|
||||
icon: 'i-heroicons-arrow-small-left-20-solid',
|
||||
label: 'Prev',
|
||||
color: 'gray'
|
||||
}"
|
||||
:next-button="{
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid',
|
||||
trailing: true,
|
||||
label: 'Next',
|
||||
color: 'gray'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div dir="rtl">
|
||||
<UPagination
|
||||
v-model="page"
|
||||
:total="items.length"
|
||||
:prev-button="{
|
||||
icon: 'i-heroicons-arrow-small-left-20-solid',
|
||||
label: 'السابق',
|
||||
color: 'gray'
|
||||
}"
|
||||
:next-button="{
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid',
|
||||
trailing: true,
|
||||
label: 'التالي',
|
||||
color: 'gray'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
21
docs/components/content/themes/PaginationThemeRounded.vue
Normal file
21
docs/components/content/themes/PaginationThemeRounded.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<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>
|
||||
@@ -1,13 +1,22 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Introduction',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
label: 'Theming',
|
||||
to: '/getting-started/theming'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
label: 'Shortcuts',
|
||||
to: '/getting-started/shortcuts'
|
||||
}, {
|
||||
label: 'Examples',
|
||||
to: '/getting-started/examples'
|
||||
}, {
|
||||
label: 'Roadmap',
|
||||
to: '/getting-started/roadmap'
|
||||
}]
|
||||
</script>
|
||||
|
||||
@@ -15,9 +24,9 @@ const links = [{
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
padding: 'pl-4',
|
||||
wrapper: 'border-s border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-s -ms-px lg:leading-6',
|
||||
padding: 'ps-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
|
||||
@@ -1,32 +1,15 @@
|
||||
<template>
|
||||
<aside class="hidden pb-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-64px)] lg:sticky lg:pr-8 lg:pl-[2px]">
|
||||
<aside class="hidden py-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-65px)] lg:sticky lg:pr-8 lg:pl-[2px]">
|
||||
<div class="relative">
|
||||
<div class="sticky top-0 pointer-events-none z-[1]">
|
||||
<!-- <div class="sticky top-0 pointer-events-none z-[1]">
|
||||
<div class="h-8 bg-white dark:bg-gray-900" />
|
||||
<div class="bg-white dark:bg-gray-900 relative pointer-events-auto">
|
||||
<UButton
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
class="w-full"
|
||||
color="gray"
|
||||
@click="isSearchModalOpen = true"
|
||||
>
|
||||
Search
|
||||
|
||||
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</UButton>
|
||||
<DocsSearchButton class="w-full" />
|
||||
</div>
|
||||
<div class="h-8 bg-gradient-to-b from-white dark:from-gray-900" />
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<DocsAsideLinks />
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<div v-for="(group, index) in navigation" :key="index" class="space-y-3">
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-gray-200">
|
||||
<span class="truncate">{{ group.title }}</span>
|
||||
</div>
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-gray-200 truncate leading-6">
|
||||
{{ group.title }}
|
||||
</p>
|
||||
|
||||
<UVerticalNavigation
|
||||
:links="mapContentLinks(group.children)"
|
||||
class="mt-1"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
base: 'group block border-l -ml-px lg:leading-6 flex items-center gap-2',
|
||||
padding: 'pl-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
/>
|
||||
>
|
||||
<template #badge="{ link }">
|
||||
<UBadge v-if="link.badge" size="xs" :ui="{ rounded: 'rounded-full' }">
|
||||
{{ link.badge }}
|
||||
</UBadge>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,9 +32,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const { navigation } = useContent() as { navigation: NavItem[] }
|
||||
const navigation: Ref<NavItem[]> = inject('navigation')
|
||||
|
||||
function mapContentLinks (links: NavItem[]) {
|
||||
return links?.map(link => ({ label: link.title, icon: link.icon, to: link._path })) || []
|
||||
return links?.map(link => ({ label: link.title, icon: link.icon, to: link._path, badge: link.badge })) || []
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<footer class="flex items-center justify-end gap-1.5">
|
||||
<footer class="flex items-center justify-between gap-1.5">
|
||||
<div class="flex items-baseline gap-1.5 text-sm text-center text-gray-500 dark:text-gray-400">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs">
|
||||
<LogoLabs class="text-primary-500 w-14 h-auto dark:text-primary-400" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<NuxtLink to="https://github.com/nuxtlabs/ui/releases" target="_blank">
|
||||
<UBadge label="v2.4.0" />
|
||||
</NuxtLink>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
@@ -13,5 +13,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { page } = useContent()
|
||||
defineProps({
|
||||
page: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
|
||||
<div class="flex items-center gap-2 mt-4 lg:mt-0">
|
||||
<div v-if="page.headlessui || page.github" class="flex items-center gap-2 mt-4 lg:mt-0">
|
||||
<UButton
|
||||
v-if="page.headlessui"
|
||||
:label="page.headlessui.label"
|
||||
@@ -22,16 +22,21 @@
|
||||
label="GitHub"
|
||||
icon="i-simple-icons-github"
|
||||
color="white"
|
||||
:to="`https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/${page._dir}/${page.title.replace(' ', '')}.vue`"
|
||||
:to="`https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/${page._dir}/${page.title.replace(' ', '')}${page.github.suffix || '.vue'}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="page.description" class="mt-4 text-lg">
|
||||
<p v-if="page.description" class="mt-4 text-lg text-gray-500 dark:text-gray-400">
|
||||
{{ page.description }}
|
||||
</p>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { page } = useContent()
|
||||
defineProps({
|
||||
page: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<DocsPrevNextCard v-if="prev" :title="prev.navigation?.title || prev.title" :description="prev.navigation?.description || prev.description" :to="prev._path" icon="i-heroicons-arrow-left-20-solid" />
|
||||
<DocsPrevNextCard
|
||||
v-if="prev"
|
||||
:title="prev.title"
|
||||
:description="prev.description"
|
||||
:to="prev._path"
|
||||
icon="i-heroicons-arrow-left-20-solid"
|
||||
/>
|
||||
<span v-else class="hidden sm:block"> </span>
|
||||
<DocsPrevNextCard
|
||||
v-if="next"
|
||||
:title="next.navigation?.title || next.title"
|
||||
:description="next.navigation?.description || next.description"
|
||||
:title="next.title"
|
||||
:description="next.description"
|
||||
:to="next._path"
|
||||
icon="i-heroicons-arrow-right-20-solid"
|
||||
class="text-right"
|
||||
@@ -14,5 +20,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { prev, next } = useContent()
|
||||
defineProps({
|
||||
prev: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
next: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NuxtLink :to="to" class="block px-5 py-8 border not-prose rounded-lg border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/25 group">
|
||||
<NuxtLink :to="to" class="block px-5 py-8 border not-prose rounded-lg border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50 group">
|
||||
<div v-if="icon" class="inline-flex items-center rounded-full p-1.5 bg-gray-50 dark:bg-gray-800 group-hover:bg-primary-50 dark:group-hover:bg-primary-400/10 ring-1 ring-gray-300 dark:ring-gray-700 mb-4 group-hover:ring-primary-500/50 dark:group-hover:ring-primary-400/50">
|
||||
<UIcon :name="icon" class="w-5 h-5 text-gray-900 dark:text-gray-100 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
command-attribute="title"
|
||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'ghost', size: 'sm', class: '-mr-1.5' }"
|
||||
:ui="{ input: { height: 'h-16 sm:h-12', icon: { size: 'h-5 w-5', padding: 'pl-11' } } }"
|
||||
:fuse="{
|
||||
fuseOptions: { ignoreLocation: true, includeMatches: true, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
|
||||
resultLimit: 10
|
||||
@@ -23,9 +25,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
import type { Command } from '../../../src/runtime/types'
|
||||
|
||||
const { navigation } = useContent()
|
||||
const navigation: Ref<NavItem[]> = inject('navigation')
|
||||
|
||||
const router = useRouter()
|
||||
const { usingInput } = useShortcuts()
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
|
||||
33
docs/components/docs/DocsSearchButton.vue
Normal file
33
docs/components/docs/DocsSearchButton.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<UButton
|
||||
color="white"
|
||||
variant="outline"
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
label="Search..."
|
||||
truncate
|
||||
:ui="{
|
||||
color: {
|
||||
white: {
|
||||
outline: 'ring-1 ring-inset ring-gray-200 dark:ring-gray-800 hover:ring-gray-300 dark:hover:ring-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/50 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
|
||||
}
|
||||
}
|
||||
}"
|
||||
@click="isSearchModalOpen = true"
|
||||
>
|
||||
<template #trailing>
|
||||
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1 flex-shrink-0">
|
||||
<UKbd class="!text-inherit">
|
||||
{{ metaSymbol }}
|
||||
</UKbd>
|
||||
<UKbd class="!text-inherit">
|
||||
K
|
||||
</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
@@ -13,7 +13,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { toc } = useContent()
|
||||
defineProps({
|
||||
toc: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const isTocOpen = ref(false)
|
||||
</script>
|
||||
|
||||
@@ -17,6 +17,7 @@ This module has been developed by the [NuxtLabs](https://nuxtlabs.com/) team for
|
||||
- Built with [Headless UI](https://headlessui.dev/) and [Tailwind CSS](https://tailwindcss.com/)
|
||||
- HMR support through Nuxt App Config
|
||||
- Dark mode support
|
||||
- Support for LTR and RTL languages
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
|
||||
@@ -38,6 +38,53 @@ As this module installs [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/) a
|
||||
|
||||
:u-button{icon="i-simple-icons-stackblitz" label="Play on StackBlitz" size="lg" to="https://stackblitz.com/edit/nuxtlabs-ui?file=app.config.ts,app.vue" target="_blank"}
|
||||
|
||||
## IntelliSense
|
||||
|
||||
If you're using VSCode, you can install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension to get autocompletion for the classes.
|
||||
|
||||
You can read more on how to set it up on the [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/tailwind/editor-support) module documentation, but to summarize, you'll need to add the following to your `settings.json`:
|
||||
|
||||
```json [settings.json]
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can write your `tailwind.config` in TypeScript as such:
|
||||
|
||||
```ts [tailwind.config.ts]
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
export default <Partial<Config>> {
|
||||
content: [
|
||||
'docs/content/**/*.md'
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If you do so, you'll need to add the following to your `settings.json`:
|
||||
|
||||
```json [settings.json]
|
||||
{
|
||||
"tailwindCSS.experimental.configFile": "tailwind.config.ts"
|
||||
}
|
||||
```
|
||||
|
||||
Also, the extension won't work when writing classes in your `app.config.ts` by default. You can add the following to your `settings.json` to fix this:
|
||||
|
||||
```json [settings.json]
|
||||
{
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Key | Default | Description |
|
||||
@@ -45,6 +92,7 @@ As this module installs [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/) a
|
||||
| `prefix` | `u` | Define the prefix of the imported components. |
|
||||
| `global` | `false` | Expose components globally. |
|
||||
| `icons` | `['heroicons']` | Icon collections to load. |
|
||||
| `safelistColors` | `['primary']` | Force safelisting of colors. |
|
||||
|
||||
## Edge
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export default defineAppConfig({
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Try to change the `primary` and `gray` colors in the navbar and see the documentation change live.
|
||||
Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i-heroicons-swatch-20-solid" class="w-4 h-4 align-middle text-primary-500 dark:text-primary-400"} button in the header.
|
||||
::
|
||||
|
||||
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`.
|
||||
@@ -33,13 +33,63 @@ Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it
|
||||
We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
|
||||
::
|
||||
|
||||
Components that have a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style) 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](/elements/input#style) (inherited in [Select](/forms/select) and [SelectMenu](/forms/select-menu)), [Radio](/forms/radio), [Checkbox](/forms/checkbox), [Toggle](/forms/toggle), [Range](/forms/range) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
|
||||
|
||||
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.
|
||||
|
||||
The module uses the [Tailwind CSS safelist](https://tailwindcss.com/docs/content-configuration#safelisting-classes) feature to force the generation of all the classes for the `primary` color **only** as it is the default color for all the components.
|
||||
|
||||
Then, the module will automatically detect when you use one of those components with a color and will safelist it for you. This means that if you use a `red` color for a Button component, the `red` color classes will be safelisted for the Button component only. This will allow to keep the CSS bundle size as small as possible.
|
||||
|
||||
There is one case where you would want to force the safelisting of a color. For example, if you've set the default color of the Button component to `orange` in your `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
button: {
|
||||
default: {
|
||||
color: 'orange'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This will apply the orange color when using a default `<UButton />`. You'll need to safelist this color manually in your `nuxt.config.ts` ui options as we won't be able to detect it automatically. You can do so through the `safelistColors` option which defaults to `['primary']`.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
safelistColors: ['orange']
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This can also happen when you bind a dynamic color to a component: `<UBadge :color="color" />`, `<UAvatar :chip-color="statuses[user.status]" />`, etc. In this case, you'll need to safelist the possible color values manually as well.
|
||||
|
||||
## Dark mode
|
||||
|
||||
All the components are styled with dark mode in mind.
|
||||
|
||||
Thanks to [Tailwind CSS dark mode](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) `class` strategy and the [@nuxtjs/color-mode](https://github.com/nuxt-modules/color-mode) module, you literally have nothing to do.
|
||||
Thanks to [Tailwind CSS dark mode](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) class strategy and the [@nuxtjs/color-mode](https://github.com/nuxt-modules/color-mode) module, you literally have nothing to do.
|
||||
|
||||
::alert{icon="i-heroicons-puzzle-piece"}
|
||||
Learn how to build a color mode button in the [Examples](/getting-started/examples#color-mode-button) page.
|
||||
::
|
||||
|
||||
You can disable dark mode by setting the `preference` to `light` instead of `system` in your `nuxt.config.ts`.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
colorMode: {
|
||||
preference: 'light'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
If you're stuck in dark mode even after changing this setting, you might need to remove the `nuxt-color-mode` entry from your browser's local storage.
|
||||
::
|
||||
|
||||
## Components
|
||||
|
||||
@@ -166,6 +216,7 @@ export default defineAppConfig({
|
||||
},
|
||||
select: {
|
||||
default: {
|
||||
loadingIcon: 'i-octicon-sync-24',
|
||||
trailingIcon: 'i-octicon-chevron-down-24'
|
||||
}
|
||||
},
|
||||
@@ -176,7 +227,7 @@ export default defineAppConfig({
|
||||
},
|
||||
notification: {
|
||||
default: {
|
||||
close: {
|
||||
closeButton: {
|
||||
icon: 'i-octicon-x-24'
|
||||
}
|
||||
}
|
||||
@@ -186,10 +237,35 @@ export default defineAppConfig({
|
||||
icon: 'i-octicon-search-24',
|
||||
loadingIcon: 'i-octicon-sync-24',
|
||||
selectedIcon: 'i-octicon-check-24',
|
||||
empty: {
|
||||
emptyState: {
|
||||
icon: 'i-octicon-search-24'
|
||||
}
|
||||
}
|
||||
},
|
||||
table: {
|
||||
default: {
|
||||
sortAscIcon: 'i-octicon-sort-asc-24',
|
||||
sortDescIcon: 'i-octicon-sort-desc-24',
|
||||
sortButton: {
|
||||
icon: 'i-octicon-arrow-switch-24'
|
||||
},
|
||||
loadingState: {
|
||||
icon: 'i-octicon-sync-24'
|
||||
},
|
||||
emptyState: {
|
||||
icon: 'i-octicon-database-24'
|
||||
}
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
default: {
|
||||
prevButton: {
|
||||
icon: 'i-octicon-arrow-left-24'
|
||||
},
|
||||
nextButton: {
|
||||
icon: 'i-octicon-arrow-right-24'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -48,6 +48,21 @@ defineShortcuts({
|
||||
</script>
|
||||
```
|
||||
|
||||
Shortcuts keys are written as the literal keyboard key value. Combinations are made with `_` separator. Chained shortcuts are made with `-` separator.
|
||||
|
||||
Modifiers are also available:
|
||||
- `meta`: acts as `Command` for MacOS and `Control` for others
|
||||
- `ctrl`: acts as `Control`
|
||||
- `shift`: acts as `Shift` and is only necessary for alphabetic keys
|
||||
|
||||
Examples of keys:
|
||||
- `escape`: will trigger by hitting `Esc`
|
||||
- `meta_k`: will trigger by hitting `⌘` and `K` at the same time on MacOS, and `Ctrl` and `K` on Windows and Linux
|
||||
- `ctrl_k`: will trigger by hitting `Ctrl` and `K` at the same time on MacOS, Windows and Linux
|
||||
- `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
|
||||
|
||||
### `usingInput`
|
||||
|
||||
Prop: `usingInput?: string | boolean`
|
||||
|
||||
258
docs/content/1.getting-started/5.examples.md
Normal file
258
docs/content/1.getting-started/5.examples.md
Normal file
@@ -0,0 +1,258 @@
|
||||
---
|
||||
title: Examples
|
||||
description: Discover some real-life examples of components you can build.
|
||||
---
|
||||
|
||||
::alert{icon="i-heroicons-wrench-screwdriver"}
|
||||
If you have any ideas of examples you'd like to see, please comment on [this issue](https://github.com/nuxtlabs/ui/issues/297).
|
||||
::
|
||||
|
||||
## Components
|
||||
|
||||
You can mix and match components to build your own UI.
|
||||
|
||||
### ColorModeButton
|
||||
|
||||
You can easily build a color mode button by using the `useColorMode` composable from `@nuxtjs/color-mode`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:color-mode-button
|
||||
|
||||
#code
|
||||
```vue [components/ColorModeButton.vue]
|
||||
<script setup>
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### DatePicker
|
||||
|
||||
Here is an example of a date picker component built with [v-calendar](https://github.com/nathanreyes/v-calendar).
|
||||
|
||||
```vue [components/DatePicker.vue]
|
||||
<script setup lang="ts">
|
||||
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
|
||||
import 'v-calendar/dist/style.css'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:model-value', 'close'])
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed(() => colorMode.value === 'dark')
|
||||
|
||||
const date = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:model-value', value)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = [{
|
||||
key: 'today',
|
||||
highlight: {
|
||||
color: 'blue',
|
||||
fillMode: 'outline',
|
||||
class: '!bg-gray-100 dark:!bg-gray-800'
|
||||
},
|
||||
dates: new Date()
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCalendarDatePicker
|
||||
v-model="date"
|
||||
transparent
|
||||
borderless
|
||||
:attributes="attrs"
|
||||
:is-dark="isDark"
|
||||
title-position="left"
|
||||
trim-weeks
|
||||
:first-day-of-week="2"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
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>
|
||||
```
|
||||
::
|
||||
|
||||
## Theming
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the components.
|
||||
|
||||
### CommandPalette
|
||||
|
||||
Here is some examples of what you can do with the [CommandPalette](/navigation/command-palette).
|
||||
|
||||
#### Algolia
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
#### Raycast
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
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',
|
||||
padding: '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>
|
||||
```
|
||||
::
|
||||
|
||||
### 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>
|
||||
```
|
||||
::
|
||||
|
||||
### LTR and RTL
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:l-t-r-and-r-t-l-theme
|
||||
::
|
||||
@@ -29,13 +29,15 @@ baseProps:
|
||||
|
||||
### Chip
|
||||
|
||||
Use the `chip-color` and `chip-position` props to display a chip on the Avatar.
|
||||
Use the `chip-color`, `chip-text` :u-badge{label="Edge" class="!rounded-full"} and `chip-position` props to display a chip on the Avatar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
chipColor: 'primary'
|
||||
chipText: ''
|
||||
chipPosition: 'top-right'
|
||||
size : 'sm'
|
||||
extraColors:
|
||||
- gray
|
||||
baseProps:
|
||||
@@ -54,6 +56,7 @@ If there's an `alt` prop initials will be displayed on top of the background, cu
|
||||
---
|
||||
props:
|
||||
alt: 'Benjamin Canac'
|
||||
size: 'sm'
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -275,6 +275,48 @@ code: |
|
||||
:u-button{icon="i-heroicons-chevron-down-20-solid" color="gray"}
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `leading`
|
||||
|
||||
Use the `#leading` slot to set the content of the leading icon.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
baseProps:
|
||||
color: 'gray'
|
||||
props:
|
||||
label: Button
|
||||
color: 'gray'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
#leading
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
|
||||
Use the `#trailing` slot to set the content of the trailing icon.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
trailing: <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
props:
|
||||
label: Button
|
||||
color: 'gray'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
#trailing
|
||||
:u-icon{name="i-heroicons-arrow-right-20-solid"}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -8,9 +8,20 @@ headlessui:
|
||||
|
||||
## Usage
|
||||
|
||||
Pass an array of arrays to the `items` prop of the Dropdown component. Each array represents a group of items. Each item can have the following properties:
|
||||
|
||||
- `label` - The label of the item.
|
||||
- `icon` - The icon of the item.
|
||||
- `avatar` - The avatar of the item. You can pass all the props of the [Avatar](/elements/avatar) component.
|
||||
- `shortcuts` - The shortcuts of the item.
|
||||
- `disabled` - Whether the item is disabled.
|
||||
- `click` - The click handler of the item.
|
||||
|
||||
You can also pass properties from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `to`, `exact`, etc.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example
|
||||
:dropdown-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
@@ -55,6 +66,35 @@ const items = [
|
||||
```
|
||||
::
|
||||
|
||||
### Mode
|
||||
|
||||
Use the `mode` prop to switch between `click` and `hover` modes.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example-mode
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<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>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -5,11 +5,22 @@ description: Display an input field.
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
---
|
||||
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>
|
||||
```
|
||||
::
|
||||
|
||||
### Style
|
||||
@@ -142,6 +153,75 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `leading`
|
||||
|
||||
Use the `#leading` slot to set the content of the leading icon.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
#leading
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
|
||||
Use the `#trailing` slot to set the content of the trailing icon.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
trailing: <span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
#trailing
|
||||
[EUR]{class="text-gray-500 dark:text-gray-400 text-xs"}
|
||||
::
|
||||
|
||||
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>
|
||||
```
|
||||
::
|
||||
|
||||
::alert{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.
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -5,11 +5,22 @@ description: Display a textarea field.
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
---
|
||||
Use a `v-model` to make the Textarea reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:textarea-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTextarea v-model="value" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Style
|
||||
@@ -85,6 +96,20 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Rows
|
||||
|
||||
Use the `rows` prop to set the number of rows of the Textarea.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
rows: 1
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Textarea.
|
||||
@@ -99,6 +124,35 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Autoresize
|
||||
|
||||
Use the `autoresize` prop to enable the autoresize. Writing more lines than the `rows` prop will make the Textarea grow up.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
modelValue: 'Here is an autoresize Textarea, write new lines to make the Textarea grow up...'
|
||||
props:
|
||||
autoresize: true
|
||||
---
|
||||
::
|
||||
|
||||
### Resize
|
||||
|
||||
Use the `resize` prop to enable the resize control.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
resize: true
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -7,19 +7,53 @@ description: Display a select field.
|
||||
|
||||
The Select component is a wrapper around the native `<select>` HTML element. For more advanced use cases like searching or multiple selection, consider using the [SelectMenu](/forms/select-menu) component.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
modelValue: 'United States'
|
||||
props:
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
excludedProps:
|
||||
- options
|
||||
---
|
||||
Use a `v-model` to make the Select reactive alongside the `options` prop to pass an array of strings or objects.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const countries = ['United States', 'Canada', 'Mexico']
|
||||
|
||||
const country = ref(countries[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model="country" :options="countries" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
When using objects, you can configure which field will be used for display through the `option-attribute` prop that defaults to `label` and which field will be used for comparison through the `value-attribute` prop that defaults to `value`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-example-objects
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const countries = [{
|
||||
name: 'United States',
|
||||
value: 'US'
|
||||
}, {
|
||||
name: 'Canada',
|
||||
value: 'CA'
|
||||
}, {
|
||||
name: 'Mexico',
|
||||
value: 'MX'
|
||||
}]
|
||||
|
||||
const country = ref('CA')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model="country" :options="countries" option-attribute="name" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Style
|
||||
@@ -157,6 +191,69 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` prop to show a loading icon and disable the Input.
|
||||
|
||||
Use the `loading-icon` prop to set a different icon or change it globally in `ui.select.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
loading: true
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `leading`
|
||||
|
||||
Use the `#leading` slot to set the content of the leading icon.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
#leading
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
|
||||
Use the `#trailing` slot to set the content of the trailing icon.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
trailing: <UIcon name="i-heroicons-arrows-up-down-20-solid" />
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
#trailing
|
||||
:u-icon{name="i-heroicons-arrows-up-down-20-solid"}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -30,7 +30,9 @@ const selected = ref(people[0])
|
||||
```
|
||||
::
|
||||
|
||||
You can use the `multiple` prop to select multiple values but you have to override the `#label` slot and handle the display yourself.
|
||||
### Multiple
|
||||
|
||||
You can use the `multiple` prop to select multiple values.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
@@ -39,47 +41,18 @@ You can use the `multiple` prop to select multiple values but you have to overri
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
const people = [...]
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple>
|
||||
<template #label>
|
||||
<span v-if="selected.length" class="font-medium truncate">{{ selected.join(', ') }}</span>
|
||||
<span v-else class="block truncate text-gray-400 dark:text-gray-500">Select people</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<USelectMenu v-model="selected" :options="people" multiple placeholder="Select people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can also override the default slot entirely.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-button{class="max-w-[12rem] w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[3])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton>
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
### Objects
|
||||
|
||||
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `option-attribute` prop that defaults to `label`.
|
||||
|
||||
@@ -134,10 +107,6 @@ const selected = ref(people[0])
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `trailing-icon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
|
||||
Use the `selected-icon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
|
||||
::component-card
|
||||
@@ -147,16 +116,16 @@ baseProps:
|
||||
placeholder: 'Select a person'
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
color: 'white'
|
||||
extraColors:
|
||||
- white
|
||||
- gray
|
||||
selectedIcon: 'i-heroicons-hand-thumb-up-solid'
|
||||
excludedProps:
|
||||
- icon
|
||||
- selectedIcon
|
||||
---
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Learn how to customize icons from the [Select](/forms/select#icon) component.
|
||||
::
|
||||
|
||||
### Search
|
||||
|
||||
Use the `searchable` prop to enable search.
|
||||
@@ -177,6 +146,63 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `label`
|
||||
|
||||
You can override the `#label` slot and handle the display yourself.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-multiple-slot{class="max-w-[12rem] w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple>
|
||||
<template #label>
|
||||
<span v-if="selected.length" class="truncate">{{ selected.join(', ') }}</span>
|
||||
<span v-else>Select people</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### `default`
|
||||
|
||||
You can also override the `#default` slot entirely.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-button{class="max-w-[12rem] w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const selected = ref(people[3])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton color="gray">
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -5,11 +5,22 @@ description: Display a checkbox field.
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox'
|
||||
---
|
||||
Use a `v-model` to make the Checkbox reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:checkbox-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const selected = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCheckbox v-model="selected" name="notifications" label="Notifications" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Label
|
||||
@@ -25,6 +36,20 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the style of the Checkbox.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox2'
|
||||
label: 'Label'
|
||||
props:
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to display a red star next to the label.
|
||||
@@ -32,7 +57,7 @@ Use the `required` prop to display a red star next to the label.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox2'
|
||||
name: 'checkbox3'
|
||||
props:
|
||||
label: 'Label'
|
||||
required: true
|
||||
@@ -46,7 +71,7 @@ Use the `help` prop to display some text under the Checkbox.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox3'
|
||||
name: 'checkbox4'
|
||||
props:
|
||||
label: 'Label'
|
||||
help: 'Please check this box'
|
||||
|
||||
@@ -5,11 +5,36 @@ description: Display a radio field.
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio'
|
||||
---
|
||||
Use a `v-model` to make the Radio reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:radio-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<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 selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URadio v-for="method of methods" :key="method.name" v-model="selected" v-bind="method" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Label
|
||||
@@ -25,6 +50,20 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the style of the Radio.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio2'
|
||||
label: 'Label'
|
||||
props:
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to display a red star next to the label.
|
||||
@@ -32,7 +71,7 @@ Use the `required` prop to display a red star next to the label.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio2'
|
||||
name: 'radio3'
|
||||
props:
|
||||
label: 'Label'
|
||||
required: true
|
||||
@@ -46,7 +85,7 @@ Use the `help` prop to display some text under the Radio.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio3'
|
||||
name: 'radio4'
|
||||
props:
|
||||
label: 'Label'
|
||||
help: 'Please choose one'
|
||||
@@ -60,7 +99,7 @@ Use the `disabled` prop to disable the Radio.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio4'
|
||||
name: 'radio5'
|
||||
value: true
|
||||
props:
|
||||
disabled: true
|
||||
|
||||
@@ -8,24 +8,62 @@ headlessui:
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
Use a `v-model` to make the Toggle reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:toggle-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const selected = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UToggle v-model="selected" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Icon
|
||||
### Style
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon-on` and `icon-off` props by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
Use the `color` prop to change the style of the Toggle.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
iconOn: 'i-heroicons-check-20-solid'
|
||||
iconOff: 'i-heroicons-x-mark-20-solid'
|
||||
excludedProps:
|
||||
- iconOn
|
||||
- iconOff
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `on-icon` and `off-icon` props by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.toggle.default.onIcon` and `ui.toggle.default.offIcon`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
onIcon: 'i-heroicons-check-20-solid'
|
||||
offIcon: 'i-heroicons-x-mark-20-solid'
|
||||
excludedProps:
|
||||
- onIcon
|
||||
- offIcon
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Toggle.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
101
docs/content/3.forms/8.range.md
Normal file
101
docs/content/3.forms/8.range.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a range field
|
||||
navigation:
|
||||
badge: "Edge"
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use a `v-model` to make the Range reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:range-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const value = ref(50)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URange v-model="value" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the visual style of the Range.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: range'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Range.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
size: 'md'
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Range.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
### Min and Max
|
||||
|
||||
Use the `min` and `max` prop to configure the Range.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
min: 0
|
||||
max: 100
|
||||
---
|
||||
::
|
||||
|
||||
### Step
|
||||
|
||||
Use the `step` prop to change the step increment.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
step: 20
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
github:
|
||||
suffix: .ts
|
||||
description: Display a label and additional informations around a form element.
|
||||
---
|
||||
|
||||
647
docs/content/4.data/1.table.md
Normal file
647
docs/content/4.data/1.table.md
Normal file
@@ -0,0 +1,647 @@
|
||||
---
|
||||
github: true
|
||||
description: 'Display data in a table.'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `rows` prop to set the data to display in the table. By default, the table will display all the fields of the rows.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-basic{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Columns
|
||||
|
||||
Use the `columns` prop to configure which columns to display. It's an array of objects with the following properties:
|
||||
|
||||
- `label` - The label to display in the table header. Can be changed through the `column-attribute` prop.
|
||||
- `key` - The field to display from the row data.
|
||||
- `sortable` - Whether the column is sortable. Defaults to `false`.
|
||||
- `direction` - The sort direction to use on first click. Defaults to `asc`.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-columns{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'User name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Job position'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role'
|
||||
}]
|
||||
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :columns="columns" :rows="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can easily use the [SelectMenu](/forms/select-menu) component to change the columns to display.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-columns-selectable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}]
|
||||
|
||||
const selectedColumns = ref([...columns])
|
||||
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple placeholder="Columns" />
|
||||
|
||||
<UTable :columns="selectedColumns" :rows="people" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Sortable
|
||||
|
||||
You can make the columns sortable by setting the `sortable` property to `true` in the column configuration.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-columns-sortable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
sortable: true,
|
||||
direction: 'desc'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}]
|
||||
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :columns="columns" :rows="people" :sort="{ column: 'title' }" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can specify the default direction of each column through the `direction` property. It can be either `asc` or `desc` and defaults to `asc`.
|
||||
|
||||
You can specify a default sort for the table through the `sort` prop. It's an object with the following properties:
|
||||
|
||||
- `column` - The column to sort by.
|
||||
- `direction` - The sort direction. Can be either `asc` or `desc` and defaults to `asc`.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
This will set the default sort and will work even if no column is set as `sortable`.
|
||||
::
|
||||
|
||||
Use the `sort-button` prop to customize the sort button in the header. You can pass all the props of the [Button](/elements/button) component to customize it through this prop or globally through `ui.table.default.sortButton`. Its icon defaults to `i-heroicons-arrows-up-down-20-solid`.
|
||||
|
||||
::component-card{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
baseProps:
|
||||
class: 'w-full'
|
||||
columns:
|
||||
- key: 'id'
|
||||
label: 'ID'
|
||||
- key: 'name'
|
||||
label: 'Name'
|
||||
sortable: true
|
||||
- key: 'title'
|
||||
label: 'Title'
|
||||
sortable: true
|
||||
- key: 'email'
|
||||
label: 'Email'
|
||||
sortable: true
|
||||
- key: 'role'
|
||||
label: 'Role'
|
||||
rows:
|
||||
- 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'
|
||||
props:
|
||||
sortAscIcon: 'i-heroicons-arrow-up-20-solid'
|
||||
sortDescIcon: 'i-heroicons-arrow-down-20-solid'
|
||||
sortButton:
|
||||
icon: 'i-heroicons-sparkles-20-solid'
|
||||
color: 'primary'
|
||||
variant: 'outline'
|
||||
size: '2xs'
|
||||
square: false
|
||||
ui:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- sortButton
|
||||
- sortAscIcon
|
||||
- sortDescIcon
|
||||
---
|
||||
::
|
||||
|
||||
Use the `sort-asc-icon` prop to set a different icon or change it globally in `ui.table.default.sortAscIcon`. Defaults to `i-heroicons-bars-arrow-up-20-solid`.
|
||||
|
||||
Use the `sort-desc-icon` prop to set a different icon or change it globally in `ui.table.default.sortDescIcon`. Defaults to `i-heroicons-bars-arrow-down-20-solid`.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
You can also customize the entire header cell, read more in the [Slots](#slots) section.
|
||||
::
|
||||
|
||||
### Selectable
|
||||
|
||||
Use a `v-model` to make the table selectable. The `v-model` will be an array of the selected rows.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-selectable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
You can use the `by` prop to compare objects by a field instead of comparing object instances. We've replicated the behavior of Headless UI [Combobox](https://headlessui.com/vue/combobox#binding-objects-as-values).
|
||||
::
|
||||
|
||||
### Searchable
|
||||
|
||||
You can easily use the [Input](/forms/input) component to filter the rows.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-searchable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const q = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
if (!q.value) {
|
||||
return people
|
||||
}
|
||||
|
||||
return people.filter((person) => {
|
||||
return Object.values(person).some((value) => {
|
||||
return String(value).toLowerCase().includes(q.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UInput v-model="q" placeholder="Filter people..." />
|
||||
|
||||
<UTable :rows="filteredRows" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Paginable
|
||||
|
||||
You can easily use the [Pagination](/navigation/pagination) component to paginate the rows.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-paginable{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const page = ref(1)
|
||||
const pageCount = 5
|
||||
|
||||
const rows = computed(() => {
|
||||
return people.slice((page.value - 1) * pageCount, (page.value) * pageCount)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UTable :rows="rows" />
|
||||
|
||||
<UPagination v-model="page" :page-count="pageCount" :total="people.length" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Loading :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full"}
|
||||
|
||||
Use the `loading` prop to display a loading state.
|
||||
|
||||
Use the `loading-state` prop to customize the `icon` and `label` or change them globally in `ui.table.default.loadingState`.
|
||||
|
||||
You can also set it to `null` to hide the loading state.
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
baseProps:
|
||||
class: 'w-full'
|
||||
columns:
|
||||
- key: 'id'
|
||||
label: 'ID'
|
||||
- key: 'name'
|
||||
label: 'Name'
|
||||
- key: 'title'
|
||||
label: 'Title'
|
||||
- key: 'email'
|
||||
label: 'Email'
|
||||
- key: 'role'
|
||||
label: 'Role'
|
||||
props:
|
||||
loading: true
|
||||
loadingState:
|
||||
icon: 'i-heroicons-arrow-path-20-solid'
|
||||
label: "Loading..."
|
||||
excludedProps:
|
||||
- loadingState
|
||||
---
|
||||
::
|
||||
|
||||
This can be easily used with Nuxt `useAsyncData` composable.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [...]
|
||||
|
||||
const { pending, data: people } = await useLazyAsyncData('people', () => $fetch('/api/people'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" :columns="columns" :loading="pending" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### Empty
|
||||
|
||||
An empty state will be displayed when there are no results.
|
||||
|
||||
Use the `empty-state` prop to customize the `icon` and `label` or change them globally in `ui.table.default.emptyState`.
|
||||
|
||||
You can also set it to `null` to hide the empty state.
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
baseProps:
|
||||
class: 'w-full'
|
||||
columns:
|
||||
- key: 'id'
|
||||
label: 'ID'
|
||||
- key: 'name'
|
||||
label: 'Name'
|
||||
- key: 'title'
|
||||
label: 'Title'
|
||||
- key: 'email'
|
||||
label: 'Email'
|
||||
- key: 'role'
|
||||
label: 'Role'
|
||||
props:
|
||||
emptyState:
|
||||
icon: 'i-heroicons-circle-stack-20-solid'
|
||||
label: "No items."
|
||||
excludedProps:
|
||||
- emptyState
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
You can use slots to customize the header and data cells of the table.
|
||||
|
||||
### `<column>-header`
|
||||
|
||||
Use the `#<column>-header` slot to customize the header cell of a column. You will have access to the `column`, `sort` and `on-sort` properties in the slot scope.
|
||||
|
||||
The `sort` property is an object with the following properties:
|
||||
|
||||
- `field` - The field to sort by.
|
||||
- `direction` - The direction to sort by. Can be `asc` or `desc`.
|
||||
|
||||
The `on-sort` property is a function that you can call to sort the table and accepts the column as parameter.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Even though you can customize the sort button as mentioned in the [Sortable](#sortable) section, you can use this slot to completely override its behavior, with a custom dropdown for example.
|
||||
::
|
||||
|
||||
### `<column>-data`
|
||||
|
||||
Use the `#<column>-data` slot to customize the data cell of a column. You will have access to the `row` and `column` properties in the slot scope.
|
||||
|
||||
You can for example create an extra column for actions with a [Dropdown](/elements/dropdown) component inside or change the color of the rows based on a selection.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-slots{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [..., {
|
||||
key: 'actions'
|
||||
}]
|
||||
|
||||
const people = [...]
|
||||
|
||||
const items = (row) => [
|
||||
[{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
click: () => console.log('Edit', row.id)
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid'
|
||||
}], [{
|
||||
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'
|
||||
}]
|
||||
]
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" :columns="columns">
|
||||
<template #name-data="{ row }">
|
||||
<span :class="[selected.find(person => person.id === row.id) && 'text-primary-500 dark:text-primary-400']">{{ row.name }}</span>
|
||||
</template>
|
||||
|
||||
<template #actions-data="{ row }">
|
||||
<UDropdown :items="items(row)">
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-ellipsis-horizontal-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### `loading-state` :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full"}
|
||||
|
||||
Use the `#loading-state` slot to customize the loading state.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-loading-slot{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [...]
|
||||
|
||||
const people = []
|
||||
|
||||
const pending = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" :columns="columns" :loading="pending">
|
||||
<template #loading-state>
|
||||
<div class="flex items-center justify-center h-32">
|
||||
<i class="loader --6" />
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* https://codepen.io/jenning/pen/YzNmzaV */
|
||||
</style>
|
||||
```
|
||||
::
|
||||
|
||||
### `empty-state` :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full"}
|
||||
|
||||
Use the `#empty-state` slot to customize the empty state.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-empty-slot{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [...]
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" :columns="columns">
|
||||
<template #empty-state>
|
||||
<div class="flex flex-col items-center justify-center py-6 gap-3">
|
||||
<span class="italic text-sm">No one here!</span>
|
||||
<UButton label="Add people" />
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -1,90 +0,0 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a vertical navigation.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
},
|
||||
badge: 100
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-home',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
icon: 'i-heroicons-chart-bar',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
icon: 'i-heroicons-command-line',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Themes
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
|
||||
|
||||
### Tailwind
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-theme-tailwind
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
padding: 'pl-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>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
48
docs/content/5.navigation/1.vertical-navigation.md
Normal file
48
docs/content/5.navigation/1.vertical-navigation.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a vertical navigation.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
},
|
||||
badge: 100
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-home',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
icon: 'i-heroicons-chart-bar',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
icon: 'i-heroicons-command-line',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -166,7 +166,7 @@ Use the `selected-icon` prop to set a different icon or change it globally in `u
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
emptyState: null
|
||||
props:
|
||||
icon: 'i-heroicons-command-line'
|
||||
excludedProps:
|
||||
@@ -184,7 +184,7 @@ Use the `loading-icon` prop to set a different icon or change it globally in `ui
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
emptyState: null
|
||||
props:
|
||||
loading: true
|
||||
excludedProps:
|
||||
@@ -200,7 +200,7 @@ Use the `placeholder` prop to change the input placeholder
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
emptyState: null
|
||||
props:
|
||||
placeholder: 'Type a command...'
|
||||
excludedProps:
|
||||
@@ -210,33 +210,33 @@ excludedProps:
|
||||
|
||||
### Close
|
||||
|
||||
Use the `close` prop to display a close button on the right side of the input.
|
||||
Use the `close-button` prop to display a close button on the right side of the input.
|
||||
|
||||
You can pass all the props of the [Button](/elements/button) component to customize it through the `close` prop or globally through `ui.commandPalette.default.close`.
|
||||
You can pass all the props of the [Button](/elements/button) component to customize it through the `close-button` prop or globally through `ui.commandPalette.default.closeButton`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
emptyState: null
|
||||
props:
|
||||
close:
|
||||
closeButton:
|
||||
icon: 'i-heroicons-x-mark-20-solid'
|
||||
color: 'gray'
|
||||
variant: 'link'
|
||||
padded: false
|
||||
excludedProps:
|
||||
- close
|
||||
- closeButton
|
||||
---
|
||||
::
|
||||
|
||||
### Empty
|
||||
|
||||
Use the `empty` prop to display a message when there are no results.
|
||||
An empty state will be displayed when there are no results.
|
||||
|
||||
You can pass an `object` through the `empty` prop or globally through `ui.commandPalette.default.empty`. Here is the default:
|
||||
Use the `empty-state` prop to customize the `icon` and `label` or change them globally in `ui.commandPalette.default.emptyState`.
|
||||
|
||||
You can also set it to `null` to hide the empty label.
|
||||
You can also set it to `null` to hide the empty state.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -244,12 +244,12 @@ padding: false
|
||||
baseProps:
|
||||
placeholder: 'Type something to see the empty label change'
|
||||
props:
|
||||
empty:
|
||||
emptyState:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
label: "We couldn't find any items."
|
||||
queryLabel: "We couldn't find any items with that term. Please try again."
|
||||
excludedProps:
|
||||
- empty
|
||||
- emptyState
|
||||
---
|
||||
::
|
||||
|
||||
@@ -257,7 +257,7 @@ excludedProps:
|
||||
|
||||
The CommandPalette component takes care of the full-text search for you with [Fuse.js](https://fusejs.io). You can pass all the options of Fuse.js through the `fuse` prop.
|
||||
|
||||
When searching for a command, the component will look for a `label` property on the command by default. You can customize this behaviour by overriding the `command-attribute` prop. This will also affect the display of the command.
|
||||
When searching for a command, the component will look for a `label` property on the command by default. You can customize this behavior by overriding the `command-attribute` prop. This will also affect the display of the command.
|
||||
|
||||
You can also highlight the matches in the command by setting the `fuse.fuseOptions.includeMatches` to `true`. The CommandPalette component automatically takes care of the highlighting for you.
|
||||
|
||||
@@ -320,41 +320,41 @@ const groups = computed(() => {
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
The `loading` state will automatically be enabled when a `search` function is loading. You can disable this behaviour by setting the `loading-icon` prop to `null` or globally in `ui.commandPalette.default.loadingIcon`.
|
||||
The `loading` state will automatically be enabled when a `search` function is loading. You can disable this behavior by setting the `loading-icon` prop to `null` or globally in `ui.commandPalette.default.loadingIcon`.
|
||||
::
|
||||
|
||||
## Themes
|
||||
## Slots
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
|
||||
### `empty-state` :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full"}
|
||||
|
||||
### Algolia
|
||||
Use the `#empty-state` slot to customize the empty state.
|
||||
|
||||
::component-example
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
:command-palette-example-empty-slot{class="flex-1"}
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
Take a look at the component!
|
||||
::
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const groups = [...]
|
||||
</script>
|
||||
|
||||
### Raycast
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
Take a look at the component!
|
||||
<template>
|
||||
<UCommandPalette :groups="groups">
|
||||
<template #empty-state>
|
||||
<div class="flex flex-col items-center justify-center py-6 gap-3">
|
||||
<span class="italic text-sm">Nothing here!</span>
|
||||
<UButton label="Add item" />
|
||||
</div>
|
||||
</template>
|
||||
</UCommandPalette>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
155
docs/content/5.navigation/3.pagination.md
Normal file
155
docs/content/5.navigation/3.pagination.md
Normal file
@@ -0,0 +1,155 @@
|
||||
---
|
||||
github: true
|
||||
description: Add a pagination to handle pages.
|
||||
navigation:
|
||||
badge: 'New'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use a `v-model` to get a reactive page alongside a `total` which represents the total of items. You can also use the `page-count` prop to define the number of items per page which defaults to `10`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination v-model="page" :page-count="5" :total="items.length" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Max
|
||||
|
||||
Use the `max` prop to set a maximum of displayed pages. Defaults to `7`, being the minimum.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
modelValue: 1
|
||||
props:
|
||||
max: 5
|
||||
pageCount: 5
|
||||
total: 100
|
||||
excludedProps:
|
||||
- pageCount
|
||||
- total
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the buttons.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
modelValue: 1
|
||||
total: 100
|
||||
props:
|
||||
size: 'sm'
|
||||
ui:
|
||||
size:
|
||||
2xs: true
|
||||
xs: true
|
||||
sm: true
|
||||
md: true
|
||||
lg: true
|
||||
xl: true
|
||||
---
|
||||
::
|
||||
|
||||
### Active / Inactive
|
||||
|
||||
Use the `active-button` and `inactive-button` props to customize the active and inactive buttons of the Pagination.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
modelValue: 1
|
||||
total: 100
|
||||
props:
|
||||
activeButton:
|
||||
variant: 'outline'
|
||||
inactiveButton:
|
||||
color: 'gray'
|
||||
excludedProps:
|
||||
- activeButton
|
||||
- inactiveButton
|
||||
---
|
||||
::
|
||||
|
||||
### Prev / Next
|
||||
|
||||
Use the `prev-button` and `next-button` props to customize the prev and next buttons of the Pagination.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
modelValue: 1
|
||||
total: 100
|
||||
props:
|
||||
prevButton:
|
||||
icon: 'i-heroicons-arrow-small-left-20-solid'
|
||||
label: Prev
|
||||
color: 'gray'
|
||||
nextButton:
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid'
|
||||
trailing: true
|
||||
label: Next
|
||||
color: 'gray'
|
||||
excludedProps:
|
||||
- prevButton
|
||||
- nextButton
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `prev` / `next`
|
||||
|
||||
Use the `#prev` and `#next` slots to set the content of the previous and next buttons.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-example-prev-next-slots
|
||||
|
||||
#code
|
||||
```vue
|
||||
<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 #prev="{ onClick }">
|
||||
<UTooltip text="Previous page">
|
||||
<UButton icon="i-heroicons-arrow-small-left-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:first-child]:rotate-180 me-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<template #next="{ onClick }">
|
||||
<UTooltip text="Next page">
|
||||
<UButton icon="i-heroicons-arrow-small-right-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:last-child]:rotate-180 ms-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
</UPagination>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -19,6 +19,19 @@ First of all, add the `Notifications` component to your app, preferably inside `
|
||||
</template>
|
||||
```
|
||||
|
||||
This component will render the notifications at the bottom right of the screen by default. You can configure its behavior in the `app.config.ts` through `ui.notifications`:
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
notifications: {
|
||||
// Show toasts at the top right of the screen
|
||||
position: 'top-0 right-0'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Then, you can use the `useToast` composable to add notifications to your app:
|
||||
|
||||
::component-example
|
||||
@@ -36,19 +49,46 @@ const toast = useToast()
|
||||
```
|
||||
::
|
||||
|
||||
This component will render by default the notifications at the bottom right of the screen. You can configure its behaviour in the `app.config.ts` through `ui.notifications`:
|
||||
When using `toast.add`, this will push a new notification to the stack displayed in `<UNotifications />`. All the props of the `Notification` component can be passed to `toast.add`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
notifications: {
|
||||
// Show toasts at the top right of the screen
|
||||
position: 'top-0 right-0'
|
||||
}
|
||||
}
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
onMounted(() => {
|
||||
toast.add({
|
||||
id: 'update_downloaded',
|
||||
title: 'Update downloaded.',
|
||||
description: 'It will be installed on restart. Restart now?',
|
||||
icon: 'i-octicon-desktop-download-24',
|
||||
timeout: 0,
|
||||
actions: [{
|
||||
label: 'Restart',
|
||||
click: () => {
|
||||
|
||||
}
|
||||
}]
|
||||
})
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
You can also use the `Notification` component directly in your app as an alert for example.
|
||||
|
||||
### Title
|
||||
|
||||
Pass a `title` to your Notification.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 1
|
||||
timeout: 0
|
||||
props:
|
||||
title: 'Notification'
|
||||
---
|
||||
::
|
||||
|
||||
### Description
|
||||
|
||||
You can add a `description` in addition of the `title`.
|
||||
@@ -58,8 +98,8 @@ You can add a `description` in addition of the `title`.
|
||||
baseProps:
|
||||
id: 2
|
||||
timeout: 0
|
||||
props:
|
||||
title: 'Notification'
|
||||
props:
|
||||
description: 'This is a notification.'
|
||||
---
|
||||
::
|
||||
@@ -118,20 +158,20 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Color
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the progress and icon color of the Notification.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 5
|
||||
id: 6
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
timeout: 600000
|
||||
props:
|
||||
icon: 'i-heroicons-x-circle'
|
||||
color: 'red'
|
||||
icon: 'i-heroicons-check-badge'
|
||||
color: 'primary'
|
||||
extraColors:
|
||||
- gray
|
||||
excludedProps:
|
||||
@@ -187,18 +227,18 @@ function onCallback () {
|
||||
|
||||
### Close
|
||||
|
||||
Use the `close` prop to hide or customize the close button on the Notification.
|
||||
Use the `close-button` prop to hide or customize the close button on the Notification.
|
||||
|
||||
You can pass all the props of the [Button](/elements/button) component to customize it through the `close` prop or globally through `ui.notifications.default.close`.
|
||||
You can pass all the props of the [Button](/elements/button) component to customize it through the `close-button` prop or globally through `ui.notification.default.closeButton`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 6
|
||||
id: 7
|
||||
title: 'Notification'
|
||||
timeout: 0
|
||||
props:
|
||||
close:
|
||||
closeButton:
|
||||
icon: 'i-heroicons-archive-box-x-mark'
|
||||
color: 'primary'
|
||||
variant: 'outline'
|
||||
@@ -207,7 +247,7 @@ props:
|
||||
ui:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- close
|
||||
- closeButton
|
||||
---
|
||||
::
|
||||
|
||||
@@ -230,12 +270,12 @@ const toast = useToast()
|
||||
```
|
||||
::
|
||||
|
||||
Like for `close`, you can pass all the props of the [Button](/elements/button) component inside the action or globally through `ui.notifications.default.action`.
|
||||
Like for `closeButton`, you can pass all the props of the [Button](/elements/button) component inside the action or globally through `ui.notification.default.actionButton`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 6
|
||||
id: 8
|
||||
title: 'Notification'
|
||||
timeout: 0
|
||||
props:
|
||||
@@ -256,7 +296,7 @@ Actions will render differently whether you have a `description` set.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 6
|
||||
id: 9
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
timeout: 0
|
||||
@@ -27,10 +27,6 @@ description: Display a card for content with a header, body and footer.
|
||||
|
||||
:component-props
|
||||
|
||||
## Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="prose prose-primary dark:prose-invert max-w-none">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,4 +1,6 @@
|
||||
import ui from '../src/module'
|
||||
import { excludeColors } from '../src/colors'
|
||||
import colors from 'tailwindcss/colors'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
// @ts-ignore
|
||||
@@ -13,7 +15,6 @@ export default defineNuxtConfig({
|
||||
'nuxt-component-meta'
|
||||
],
|
||||
content: {
|
||||
documentDriven: true,
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
@@ -25,7 +26,8 @@ export default defineNuxtConfig({
|
||||
},
|
||||
ui: {
|
||||
global: true,
|
||||
icons: ['heroicons', 'simple-icons']
|
||||
icons: ['heroicons', 'simple-icons'],
|
||||
safelistColors: excludeColors(colors)
|
||||
},
|
||||
typescript: {
|
||||
strict: false,
|
||||
|
||||
53
docs/pages/[...slug].vue
Normal file
53
docs/pages/[...slug].vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div v-if="page" class="grid lg:grid-cols-10 lg:gap-8">
|
||||
<div class="pt-8 pb-16" :class="page.body?.toc ? 'lg:col-span-8' : 'lg:col-span-10'">
|
||||
<DocsPageHeader :page="page" />
|
||||
|
||||
<ContentRenderer v-if="page.body" :value="page" class="prose prose-primary dark:prose-invert max-w-none" />
|
||||
|
||||
<DocsPageFooter :page="page" class="mt-12" />
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-6">
|
||||
|
||||
<DocsPrevNext :prev="prev" :next="next" />
|
||||
|
||||
<DocsFooter class="mt-16" />
|
||||
</div>
|
||||
|
||||
<DocsToc v-if="page.body?.toc" :toc="page.body.toc" class="lg:col-span-2" />
|
||||
</div>
|
||||
<div v-else class="flex-1 flex flex-col items-center justify-center">
|
||||
<div class="text-center">
|
||||
<p class="text-base font-semibold text-primary-500 dark:text-primary-400">
|
||||
404
|
||||
</p>
|
||||
<h1 class="mt-2 text-4xl tracking-tight font-extrabold u-text-gray-900 sm:text-5xl">
|
||||
Page not found
|
||||
</h1>
|
||||
<p class="mt-2 text-base u-text-gray-500">
|
||||
Sorry, we couldn’t find the page you’re looking for.
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<NuxtLink to="/" class="text-base font-medium text-primary-500 dark:text-primary-400 hover:u-text-gray-900">
|
||||
Go back home
|
||||
<span aria-hidden="true"> →</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
const { data: page } = await useAsyncData(`docs-${route.path}`, () => queryContent(route.path).findOne())
|
||||
const { data: surround } = await useAsyncData(`docs-${route.path}-surround`, () => queryContent()
|
||||
.only(['_path', 'title', 'navigation', 'description'])
|
||||
.where({ _extension: 'md', navigation: { $ne: false } })
|
||||
.findSurround(route.path.endsWith('/') ? route.path.slice(0, -1) : route.path)
|
||||
)
|
||||
|
||||
const [prev, next] = surround.value
|
||||
|
||||
useContentHead(page)
|
||||
</script>
|
||||
42
docs/plugins/ui.ts
Normal file
42
docs/plugins/ui.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { hexToRgb } from "../../src/runtime/utils"
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
enforce: 'post',
|
||||
setup () {
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const root = computed(() => {
|
||||
const primary: Record<string, string> | undefined = colors[appConfig.ui.primary]
|
||||
const gray: Record<string, string> | undefined = colors[appConfig.ui.gray]
|
||||
|
||||
return `:root {
|
||||
${Object.entries(primary || colors.green).map(([key, value]) => `--color-primary-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
${Object.entries(gray || colors.cool).map(([key, value]) => `--color-gray-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
}`
|
||||
})
|
||||
|
||||
if (process.client) {
|
||||
watch(root, () => {
|
||||
window.localStorage.setItem('nuxt-ui-root', root.value)
|
||||
})
|
||||
|
||||
appConfig.ui.primary = window.localStorage.getItem('nuxt-ui-primary') || appConfig.ui.primary
|
||||
appConfig.ui.gray = window.localStorage.getItem('nuxt-ui-gray') || appConfig.ui.gray
|
||||
}
|
||||
if (process.server) {
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
innerHTML: `
|
||||
if (localStorage.getItem('nuxt-ui-root')) {
|
||||
document.querySelector('style#nuxt-ui-colors').innerHTML = localStorage.getItem('nuxt-ui-root')
|
||||
}`.replace(/\s+/g, ' '),
|
||||
type: 'text/javascript',
|
||||
tagPriority: -1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -50,7 +50,7 @@ export default <Partial<Config>> {
|
||||
borderRadius: '0.375rem',
|
||||
border: '1px solid var(--tw-prose-pre-border)',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-words'
|
||||
wordBreak: 'break-word'
|
||||
},
|
||||
code: {
|
||||
backgroundColor: 'var(--tw-prose-pre-bg)',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user