Compare commits

..

169 Commits

Author SHA1 Message Date
Benjamin Canac
10a9a3ea2b chore(release): 2.5.0 2023-06-27 17:05:34 +02:00
Benjamin Canac
1ff11ac1a3 feat(Table): reset sort on third click
Resolves #300
2023-06-27 15:32:19 +02:00
Benjamin Canac
07b27a228d fix(Table): default sortButton icon
Fixes 0f3fe0d54e
2023-06-27 15:31:31 +02:00
Benjamin Canac
6be9290f68 fix(FormGroup): prevent overriding color of children
Resolves #352
2023-06-27 15:17:53 +02:00
Benjamin Canac
0f3fe0d54e fix(Table): missing default sort icon when overriding sort-button prop 2023-06-27 15:11:32 +02:00
Benjamin Canac
0815f688ed chore(deps): bump 2023-06-27 14:34:07 +02:00
Benjamin Canac
8399ffe1f1 docs(installation): add IntelliSense section 2023-06-27 13:14:07 +02:00
Haytham A. Salama
91f6103719 docs: add support for RTL and LTR (#348) 2023-06-27 12:42:41 +02:00
Haytham A. Salama
278a1ea93c chore: improve RTL support (#334)
Co-authored-by: Hassan Kadhim <hassan57905@gmail.com>
2023-06-23 23:19:28 +02:00
Benjamin Canac
3f27c0ccae chore(deps): revert node engines bump 2023-06-23 22:43:37 +02:00
renovate[bot]
5a2f46683a chore(deps): update all non-major dependencies (#136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-23 22:36:35 +02:00
Benjamin Canac
c8a0005253 chore(github): use pnpm 8 2023-06-23 21:35:18 +02:00
Haytham A. Salama
881f3547f2 docs(ComponentCard): preview component only (#302) 2023-06-22 18:38:04 +02:00
Benjamin Canac
8c99b871e2 docs(Avatar): add edge badge on chip-text prop 2023-06-22 14:53:45 +02:00
Benjamin Canac
41b85d50a8 fix(components): prefix @headlessui/vue components
Resolves #315
2023-06-22 13:01:58 +02:00
JPB
759af058df feat(Avatar): handle chipText (#306)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-22 12:24:56 +02:00
Benjamin Canac
48636363d1 chore(release-it): add header to changelog 2023-06-21 19:09:11 +02:00
Hassan Kadhim
4ea114a4d6 feat: RTL support (#320) 2023-06-21 19:09:11 +02:00
Benjamin Canac
ad2349e570 chore(deps): bump 2023-06-21 19:09:11 +02:00
Haytham A. Salama
ffb312d34d feat(Radio/Checkbox/Toggle)!: handle color prop for form elements (#323)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-21 19:09:11 +02:00
TomSmith27
97a1c86433 feat(Range): new component (#290)
Co-authored-by: Tom Smith <tom.smith2711@gmail.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
Co-authored-by: Tom Smith <tom.smith@qunifi.com>
2023-06-21 19:09:11 +02:00
Benjamin Canac
c2ebb0416e fix(Toggle): add opacity-50 when disabled 2023-06-21 19:09:01 +02:00
Alex Liu
e1548062c7 chore(utils): types (#321) 2023-06-21 19:09:01 +02:00
Benjamin Canac
9cd73aa49d fix(defineShortcuts): missing useDebounceFn import 2023-06-21 19:09:01 +02:00
Benjamin Canac
a880379480 fix(defineShortcuts): missing ref import 2023-06-21 19:09:01 +02:00
TomSmith27
71c2465d7b feat(Table): pass row index to table cell (#291)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-21 19:08:51 +02:00
Benjamin Canac
0272307f28 docs: improve notification page 2023-06-21 19:08:51 +02:00
Benjamin Canac
2ea358703e docs: fix focus on raycast command palette example 2023-06-21 19:08:51 +02:00
Benjamin Canac
c458f388bb docs: improve with examples 2023-06-21 19:08:51 +02:00
Benjamin Canac
1b03b8a531 fix(Tooltip): add color in config 2023-06-21 19:08:40 +02:00
Benjamin Canac
e2f7d82d62 docs: disable documentDriven mode 2023-06-21 19:08:40 +02:00
Benjamin Canac
c8e6ed8df9 chore(deps): bump 2023-06-21 19:08:40 +02:00
Sylvain Marroufin
a67f691a00 feat(defineShortcuts): chained shortcuts + docs update (#282)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-21 19:08:32 +02:00
Benjamin Canac
dfccbcf1a9 docs: add missing icons to override in theming 2023-06-21 17:59:30 +02:00
Benjamin Canac
38ecb088ec docs: improve theming dark mode section 2023-06-21 17:59:30 +02:00
Benjamin Canac
8236b18d0d docs: edge badges to new following 2.4.0 2023-06-21 17:59:30 +02:00
Benjamin Canac
1e05b0f072 chore(release): 2.4.1 2023-06-21 17:53:38 +02:00
Benjamin Canac
87e98f038a chore(deps): migrate from standard-version to release-it 2023-06-21 17:53:21 +02:00
Benjamin Canac
f7e2082983 fix(module): safelist regex when a : was present before color
Also prevents parsing colors already safelisted initially.
2023-06-21 17:42:02 +02:00
Benjamin Canac
f719111abb fix(module): safelist aliases for input
To make it work when doing `<USelect color="yellow" />` for example
2023-06-21 17:41:51 +02:00
Benjamin Canac
3bac0874f1 fix(Radio/Checkbox): remove legacy custom 2023-06-21 17:41:39 +02:00
Selemondev
457b7a9fb7 fix(forms): precise type assertion for onInput event handler (#293)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-21 17:41:15 +02:00
Benjamin Canac
4023fbec29 fix(module): let tailwindcss viewer enabled by default
Resolves #292
2023-06-21 17:40:54 +02:00
Benjamin Canac
a274a0cdbb chore(release): 2.4.0 2023-06-13 17:42:38 +02:00
Benjamin Canac
717a514451 fix(SelectMenu): input focus after be5f352 2023-06-13 17:34:34 +02:00
9uenther
786d7765f5 fix(Table): colspan of empty and loading is wrong when selection enabled (#284) 2023-06-13 17:06:06 +02:00
Benjamin Canac
a733c13866 fix(module): hardcode gray safelist instead of deduplicate complex logic 2023-06-13 16:31:13 +02:00
Benjamin Canac
88c1930845 fix(module): transform vue files to detect multi-line components 2023-06-13 15:53:02 +02:00
Benjamin Canac
c3f5c44461 docs: improve theming colors safelisting section 2023-06-13 15:51:05 +02:00
Benjamin Canac
2cfa1f8d03 fix(module): deduplicate default safelist as components may share same rules 2023-06-13 15:27:25 +02:00
Benjamin Canac
5f7de8e595 docs: only safelist valid colors 2023-06-13 15:26:45 +02:00
Benjamin Canac
cdce519742 fix(module): only safelist known colors 2023-06-13 14:52:56 +02:00
Benjamin Canac
ccd9ca5106 fix(module): prevent safelisting dynamic :color variables 2023-06-13 14:52:32 +02:00
Benjamin Canac
9031742acc chore(deps): bump 2023-06-13 14:52:04 +02:00
Benjamin Canac
9559d0b3bc fix(deps): move @tailwindcss/container-queries to dependencies 2023-06-13 12:27:17 +02:00
Benjamin Canac
0e6550ec45 chore(deps): bump 2023-06-13 12:25:37 +02:00
Benjamin Canac
20fa4d2317 feat(module): smart safelisting (#268)
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
2023-06-13 12:18:38 +02:00
Benjamin Canac
e12e9740c9 fix(forms)!: bind $attrs to elements (#279) 2023-06-13 11:35:05 +02:00
Benjamin Canac
cbc8ef13cc fix(CommandPalette): input focus after be5f352 2023-06-12 14:56:13 +02:00
Haytham A. Salama
652af93f5c feat(CommandPalette): handle empty-state (#271)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-12 14:54:34 +02:00
Benjamin Canac
b4a96a8b01 chore(Select)!: rename text-attribute to option-attribute and defaults to label 2023-06-12 14:42:33 +02:00
Benjamin Canac
bc81d45b2b docs: improve forms usage with examples 2023-06-12 14:42:33 +02:00
Benjamin Canac
429791dab0 fix(Radio/Checkbox): split preset as indeterminate is checkbox only 2023-06-12 14:42:33 +02:00
Benjamin Canac
fe833eb2b2 fix(Toggle): missing disabled prop 2023-06-12 14:42:17 +02:00
Benjamin Canac
be5f352296 fix(module): use @tailwindcss/forms class strategy (#278) 2023-06-12 14:27:08 +02:00
Haytham A. Salama
47415322ea feat(table): add loading state (#259)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-12 14:26:29 +02:00
Benjamin Canac
d20983d355 docs: add Edge badge for next release features
Resolves #277
2023-06-12 10:53:06 +02:00
Sylvain Marroufin
f0b24ba25d feat(Pagination): new component (#257)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
Co-authored-by: Haytham A. Salama <haythamasalama@gmail.com>
2023-06-09 18:12:40 +02:00
Haytham A. Salama
f7a34c8fee feat(table): add slot for empty state (#260)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-06-09 17:06:42 +02:00
Benjamin Canac
4e5e614eb4 docs: notification color default to primary 2023-06-09 14:32:53 +02:00
Benjamin Canac
07f7855a26 fix(Notification): class priority for icon color 2023-06-09 11:40:46 +02:00
Benjamin Canac
57f95102e2 chore: add @tailwindcss/container-queries official plugin 2023-06-09 11:39:46 +02:00
Benjamin Canac
3f8d927438 chore(Dropdown): handle height for overflow 2023-06-08 12:55:38 +02:00
Benjamin Canac
d91c0bb894 fix(ButtonGroup): use -space-x-px on wrapper 2023-06-06 15:58:24 +02:00
Benjamin Canac
a6176720c7 fix(ButtonGroup): invalid size validator 2023-06-06 14:59:18 +02:00
Benjamin Canac
a6903df58f fix(Button): same size when no label + uniformize form elements 2023-06-06 12:30:03 +02:00
Benjamin Canac
19b149518e docs: add space between header icons 2023-06-06 12:26:34 +02:00
Benjamin Canac
c66a99a60f docs: only display links section in header when needed 2023-06-06 11:48:16 +02:00
Benjamin Canac
4a7c6035b6 chore(deps): bump 2023-06-06 10:33:17 +02:00
Benjamin Canac
207444fdea fix(forms): padded prop with p-0 class 2023-06-06 10:33:11 +02:00
Benjamin Canac
60eea0e46b chore(release): 2.3.0 2023-06-05 12:12:48 +02:00
Benjamin Canac
5e50eb9eb8 fix: use cloneVNode when altering props in render functions
Resolves #252

https://vuejs.org/api/render-function.html#clonevnode
2023-06-05 11:17:31 +02:00
Dominik Opyd
af65683123 docs(github): support for various file extensions (#250) 2023-06-02 11:35:31 +02:00
Benjamin Canac
2c673f5377 fix(CommandPalette): override of closeButton and emptyState props 2023-06-01 17:15:04 +02:00
Benjamin Canac
192b0e6301 fix(Table): override of sortButton and emptyState props 2023-06-01 17:14:45 +02:00
Benjamin Canac
71edb91c4f fix(Avatar): placeholder font size 2023-06-01 16:47:23 +02:00
Benjamin Canac
f9b935f5f5 fix(Badge): remove console.log in validator 2023-06-01 16:24:19 +02:00
Benjamin Canac
23833e92cb chore(Badge): handle color override like buttons 2023-06-01 16:06:07 +02:00
Benjamin Canac
241df7f05e docs: fix toc scroll when duplicated names 2023-06-01 15:41:23 +02:00
Benjamin Canac
130a1f2c54 docs: improve slots sections 2023-06-01 15:29:02 +02:00
Benjamin Canac
c63981e31c docs: :component-card now handle slots 2023-06-01 15:28:39 +02:00
Benjamin Canac
687f0c6f63 docs: improve inline code blocks inside Alert component 2023-06-01 15:28:12 +02:00
Benjamin Canac
f59a92ca15 chore(Input)!: move pointer class inside its own preset class 2023-06-01 15:27:53 +02:00
Benjamin Canac
01fa85c7a3 fix(defineShortcuts): err with input autocomplete that triggers keydown 2023-06-01 15:26:46 +02:00
Benjamin Canac
3434bc7f2b chore(deps): bump @nuxthq/studio 2023-06-01 11:43:47 +02:00
Benjamin Canac
9b1aacb1da docs: move slots sections as h2 2023-06-01 11:39:07 +02:00
Benjamin Canac
8951923a11 fix(SelectMenu): disable on loading 2023-06-01 11:08:16 +02:00
Benjamin Canac
e200d4cc74 chore(package): remove preinstall script 2023-06-01 10:49:36 +02:00
Benjamin Canac
e05619f8c8 chore: add leading and trailing slots
Resolves #246
2023-05-31 23:53:31 +02:00
Benjamin Canac
5ea43ab4e4 chore: uniformize icons in Button / Input / Select / SelectMenu
Also adds `loading` to `Select` and `SelectMenu`
2023-05-31 23:30:52 +02:00
Benjamin Canac
ba44c58a80 chore(SelectMenu)!: remove inline-flex from wrapper to behave like other form elements 2023-05-31 23:22:58 +02:00
Benjamin Canac
490025a981 docs: add Table icons in theming icons section 2023-05-31 18:31:15 +02:00
Benjamin Canac
2966373a86 chore(Table): handle empty-state
Resolves #243
2023-05-31 18:30:49 +02:00
Sylvain Marroufin
8bdb8c45f7 chore(Dropdown): hover mode with padding instead of offset + improve docs (#242)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-05-31 14:28:14 +02:00
Sylvain Marroufin
9827de0b58 docs(textarea): improve props documentation (#241) 2023-05-31 12:49:47 +02:00
Benjamin Canac
23f01fde41 chore(Table): rename preset container to base 2023-05-31 12:11:28 +02:00
Benjamin Canac
f680318e44 docs: fix overflow in table page
Fixes #244
2023-05-31 12:11:12 +02:00
Benjamin Canac
cd2d1eb1fa docs(table): add alert on sort prop without sortable 2023-05-30 18:06:03 +02:00
Benjamin Canac
3ba0aedcba fix(Table): type sort prop 2023-05-30 18:03:24 +02:00
Benjamin Canac
40b6884424 chore(Table): handle default sort and default column direction 2023-05-30 17:59:30 +02:00
Benjamin Canac
a2638c6057 chore(Table): split container divide 2023-05-30 16:23:17 +02:00
Benjamin Canac
6bd5142a37 fix(Table): add missing text-left in th.base 2023-05-30 16:21:54 +02:00
Benjamin Canac
bc1d653857 chore(Table): split preset for th and td 2023-05-30 16:17:17 +02:00
Benjamin Canac
6c215e07a6 chore(deps): bump 2023-05-30 12:18:05 +02:00
Benjamin Canac
272af9d24c fix(Table): missing ref import from vue 2023-05-30 12:17:59 +02:00
Benjamin Canac
cce000ab2b feat: add Table component (#237) 2023-05-30 12:13:57 +02:00
Benjamin Canac
4a99d6a7bb docs: fix notification preset for closeButton and actionButton 2023-05-29 22:45:33 +02:00
Benjamin Canac
4458656be5 chore(Notification)!: rename to closeButton and actionButton for consistency 2023-05-29 21:59:59 +02:00
Benjamin Canac
daca46371c chore(CommandPalette)!: rename props to emptyState and closeButton for consistency 2023-05-29 21:55:08 +02:00
Benjamin Canac
8ee2ac10e7 chore(Toggle)!: rename icons to onIcon / offIcon for consistency 2023-05-29 21:38:51 +02:00
Benjamin Canac
1ebaa5aa00 fix(Button): invalid padding when using square prop 2023-05-29 21:36:28 +02:00
Benjamin Canac
cb43548305 chore(SelectMenu): handle multiple default display + specific placeholder 2023-05-29 11:52:02 +02:00
Benjamin Canac
360084af7c chore(Toggle): improve component
- allow `iconOn` / `iconOff` default values from preset
- `bg-gray-900` on dark mode inside of `bg-white`
- added `name` prop for form control
2023-05-27 22:27:31 +02:00
Benjamin Canac
0af5184c70 chore(release): 2.2.1 2023-05-27 12:27:53 +02:00
Benjamin Canac
44c3e2c46a chore(forms): remove required on Input, Select and Textarea name
Resolves #236
2023-05-27 12:03:29 +02:00
Benjamin Canac
a96dc19215 fix(FormGroup): missing h import from vue
Resolves #236
2023-05-27 12:02:51 +02:00
Benjamin Canac
aa881a8d00 chore(release): 2.2.0 2023-05-26 23:19:53 +02:00
Benjamin Canac
08413f198b scripts: update to pnpm 2023-05-26 22:46:17 +02:00
Benjamin Canac
75ab1d2ed5 chore(deps): bump 2023-05-26 22:25:58 +02:00
Sumit Kolhe
2d6ce654f4 docs: add close button to Slideover example (#211)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-05-26 22:20:34 +02:00
Benjamin Canac
9ce531a06f feat!: handle color states on form elements (#234) 2023-05-26 22:07:49 +02:00
Benjamin Canac
1a9dc5c980 fix(Notification): remove default color on icon 2023-05-26 18:28:52 +02:00
Benjamin Canac
589f86ef1b chore(Avatar): dark variant for chip background color 2023-05-26 18:28:32 +02:00
Benjamin Canac
1b61ec72e2 chore(Notification)!: rename progressColor to color and style icon
This also removes `progressVariant` prop
2023-05-26 18:03:54 +02:00
Benjamin Canac
1f22f84360 chore(Avatar)!: remove chipVariant prop 2023-05-26 18:02:48 +02:00
Benjamin Canac
2c6db975f9 chore(deps): switch to pnpm (#228) 2023-05-26 17:41:07 +02:00
Benjamin Canac
b7099aa0d3 chore(SelectMenu): add searchablePlaceholder prop
Resolves #231
2023-05-26 15:02:21 +02:00
Benjamin Canac
36b0869bc2 docs: fix prev card gap on first page 2023-05-23 16:57:42 +02:00
Benjamin Canac
28167e41ff docs: add VerticalNavigation tailwind example 2023-05-23 15:27:13 +02:00
Benjamin Canac
19923cbf1e chore(VerticalNavigation)!: split preset 2023-05-23 15:26:47 +02:00
Benjamin Canac
1210e99ec1 chore(VerticalNavigation): improve types import 2023-05-23 15:25:28 +02:00
Benjamin Canac
fc894bc1ae chore(Dropdown): improve types import 2023-05-23 15:25:12 +02:00
Benjamin Canac
9491ac7172 chore(CommandPalette): improve types import 2023-05-23 15:25:00 +02:00
Benjamin Canac
32dc2264d8 chore(types): export button 2023-05-23 15:24:41 +02:00
Benjamin Canac
45ba3b26da chore(Notification): improve types 2023-05-23 15:24:32 +02:00
Benjamin Canac
6d3309c42d chore(Notification): move padding to app.config 2023-05-23 11:25:56 +02:00
Benjamin Canac
530b85136d docs: handle color mode in volta embed 2023-05-23 11:11:19 +02:00
Benjamin Canac
cb9ed9ad3f chore(Button): inject NuxtLink in components 2023-05-22 19:05:39 +02:00
Benjamin Canac
524e220914 chore(VerticalNavigation): improve binds & types 2023-05-22 19:05:17 +02:00
Benjamin Canac
e3e6ef27a2 docs: improve Dropdown example with click and disabled 2023-05-22 19:04:39 +02:00
Benjamin Canac
55f115f9fe chore(Dropdown): use ULinkCustom + improve item binds & types
Fixes #215
2023-05-22 19:04:18 +02:00
Benjamin Canac
bdaf2dbbd4 chore(CommandPalette): handle loading state (#221) 2023-05-22 16:00:31 +02:00
Benjamin Canac
e7eea067b2 chore(Notification): add progressColor and progressVariant props (#219)
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
2023-05-22 15:01:19 +02:00
Benjamin Canac
a56dbeab35 fix(Radio/Checkbox): remove ring offset on focus 2023-05-22 13:41:56 +02:00
Benjamin Canac
570b82d1e7 chore(Avatar): allow default value for chipColor through app.config.ts 2023-05-22 12:24:17 +02:00
Harry Yep
b5189c0c07 docs: LogoLabs not shown (#216) 2023-05-21 23:01:08 +02:00
Sébastien Chopin
8a0a5d8ba0 docs: pre-render component-meta routes 2023-05-20 19:13:58 +02:00
Sébastien Chopin
d3e5f4e15d docs: remove console.log 2023-05-20 18:53:32 +02:00
Sébastien Chopin
5a592b7ee0 docs: use CF rules for redirect 2023-05-20 18:49:39 +02:00
Sébastien Chopin
43787eca74 docs: move vercel.json to public dir 2023-05-20 18:41:07 +02:00
Sébastien Chopin
595ed9fb46 docs: add vercel redirect 2023-05-20 18:38:19 +02:00
Sébastien Chopin
5c4ab26d25 docs: support ssg 2023-05-20 18:31:56 +02:00
Sébastien Chopin
2030f24a47 docs: update logo on aside on mobile 2023-05-20 13:18:19 +02:00
Benjamin Canac
6eda322496 chore(VerticalNavigation): links badge type as number
Resolves #206
2023-05-19 15:55:18 +02:00
Benjamin Canac
318f8b2f08 docs: improve theming colors section 2023-05-19 15:00:39 +02:00
Benjamin Canac
dfab900562 docs: add badge in VerticalNavigation example 2023-05-19 14:51:31 +02:00
Benjamin Canac
d2ee5058f8 fix(VerticalNavigation): badge display
Resolves #205
2023-05-19 14:51:16 +02:00
Benjamin Canac
e358183165 docs: getting started title on index 2023-05-19 13:01:34 +02:00
Benjamin Canac
26579538f5 docs: prevent Alert text hover without link 2023-05-19 13:00:50 +02:00
155 changed files with 18114 additions and 11035 deletions

View File

@@ -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: {

View File

@@ -22,30 +22,41 @@ jobs:
with:
node-version: ${{ matrix.node }}
- name: Checkout
- name: checkout
uses: actions/checkout@master
with:
persist-credentials: false
fetch-depth: 0
- name: Cache
uses: actions/cache@v3
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
path: node_modules
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
version: 8
run_install: false
- name: Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Lint
run: yarn lint
run: pnpm run lint
- name: Typecheck
run: yarn typecheck
run: pnpm run typecheck
- name: Build
run: yarn build
run: pnpm run build
- name: Release Edge
if: github.event_name == 'push'

View File

@@ -22,30 +22,41 @@ jobs:
with:
node-version: ${{ matrix.node }}
- name: Checkout
- name: checkout
uses: actions/checkout@master
with:
persist-credentials: false
fetch-depth: 0
- name: Cache
uses: actions/cache@v3
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
path: node_modules
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
version: 8
run_install: false
- name: Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Lint
run: yarn lint
run: pnpm run lint
- name: Typecheck
run: yarn typecheck
run: pnpm run typecheck
- name: Build
run: yarn build
run: pnpm run build
- name: Version Check
id: check

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ nuxt.d.ts
dist
.DS_Store
.history
.vercel
.idea

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
shamefully-hoist=true

6
.prettierrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}

23
.release-it.json Normal file
View 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
}
}
}

View File

@@ -1,6 +1,156 @@
# 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)
### Bug Fixes
* **FormGroup:** missing `h` import from `vue` ([a96dc19](https://github.com/nuxtlabs/ui/commit/a96dc192157725143503b1a5e4b404cb48dc9d3f)), closes [#236](https://github.com/nuxtlabs/ui/issues/236)
## [2.2.0](https://github.com/nuxtlabs/ui/compare/v2.1.0...v2.2.0) (2023-05-26)
### ⚠ BREAKING CHANGES
* handle color states on form elements (#234)
* **Notification:** rename `progressColor` to `color` and style icon
* **Avatar:** remove `chipVariant` prop
* **VerticalNavigation:** split preset
### Features
* handle color states on form elements ([#234](https://github.com/nuxtlabs/ui/issues/234)) ([9ce531a](https://github.com/nuxtlabs/ui/commit/9ce531a06f1a972bc003876162e0503c1bbbdbd8))
### Bug Fixes
* **Notification:** remove default color on icon ([1a9dc5c](https://github.com/nuxtlabs/ui/commit/1a9dc5c980d8477cdf9386a17e20fc9fec0d883e))
* **Radio/Checkbox:** remove ring offset on focus ([a56dbea](https://github.com/nuxtlabs/ui/commit/a56dbeab351a5c58e5bb49f5762669e2884c6483))
* **VerticalNavigation:** badge display ([d2ee505](https://github.com/nuxtlabs/ui/commit/d2ee5058f819fc17f281f323dab2f0b3d80cf7bd)), closes [#205](https://github.com/nuxtlabs/ui/issues/205)
* **Avatar:** remove `chipVariant` prop ([1f22f84](https://github.com/nuxtlabs/ui/commit/1f22f84360c20498eea8971b21db9293a4c9c3dc))
* **Notification:** rename `progressColor` to `color` and style icon ([1b61ec7](https://github.com/nuxtlabs/ui/commit/1b61ec72e292325d7776a4719f14a75bdb18e110))
* **VerticalNavigation:** split preset ([19923cb](https://github.com/nuxtlabs/ui/commit/19923cbf1edc6c6d4aefb9ffab9f908b116e1c69))
## [2.1.0](https://github.com/nuxtlabs/ui/compare/v2.0.4...v2.1.0) (2023-05-19)
@@ -508,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))

View File

@@ -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

View File

@@ -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'
}
})

View 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>

View 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>

View 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>

View File

@@ -1,87 +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="/" 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="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white">
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
nuxthq/ui
</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>
@@ -93,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>

View 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>

View File

@@ -1,87 +0,0 @@
<template>
<div class="flex items-center shadow-sm">
<USelectMenu
v-model="primary"
name="primary"
class="w-full [&>div>button]:!rounded-r-none"
appearance="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>
<USelectMenu
v-model="gray"
name="gray"
class="w-full [&>div>button]:!rounded-l-none [&>div>button]:-ml-px"
appearance="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>
</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
}
})
</script>

View File

@@ -2,8 +2,8 @@
<component
:is="to ? NuxtLink : 'div'"
:to="to"
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200 text-sm leading-6 my-5 last:mb-0 font-normal group relative"
:class="[to ? 'hover:!border-primary-500 dark:hover:!border-primary-400 hover:text-primary-500 dark:hover:text-primary-400 border-dashed' : '']"
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" />

View 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>

View File

@@ -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"
appearance="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]"
appearance="none"
class="inline-flex"
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md' }"
:ui-select="{ custom: '!py-0' }"
:name="`prop-${prop.name}`"
variant="none"
: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"
appearance="none"
: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>
@@ -49,6 +55,7 @@
// @ts-expect-error
import { transformContent } from '@nuxt/content/transformers'
// eslint-disable-next-line vue/no-dupe-keys
const props = defineProps({
slug: {
type: String,
@@ -66,6 +73,10 @@ const props = defineProps({
type: String,
default: null
},
slots: {
type: Object,
default: null
},
baseProps: {
type: Object,
default: () => ({})
@@ -78,17 +89,31 @@ const props = defineProps({
type: Array,
default: () => []
},
extraColors: {
type: Array,
default: () => []
},
backgroundClass: {
type: String,
default: 'bg-white dark:bg-gray-900'
},
overflowClass: {
type: String,
default: ''
},
previewOnly: {
type: Boolean,
default: false
}
})
// eslint-disable-next-line vue/no-dupe-keys
const baseProps = reactive({ ...props.baseProps })
const componentProps = reactive({ ...props.props })
const appConfig = useAppConfig()
const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`
@@ -97,9 +122,10 @@ const meta = await fetchComponentMeta(name)
// Computed
// 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) => {
@@ -117,7 +143,8 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
const keys = useGet(ui.value, dottedKey, {})
let options = typeof keys === 'object' && Object.keys(keys)
if (key.toLowerCase().endsWith('color')) {
options = appConfig.ui.colors
// @ts-ignore
options = [...appConfig.ui.colors, ...props.extraColors]
}
return {
@@ -128,6 +155,7 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
}
}).filter(Boolean))
// eslint-disable-next-line vue/no-dupe-keys
const code = computed(() => {
let code = `\`\`\`html
<${name}`
@@ -140,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 += `>
@@ -173,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',

View File

@@ -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>

View File

@@ -15,6 +15,7 @@ const props = defineProps({
const appConfig = useAppConfig()
const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`

View File

@@ -43,6 +43,7 @@ const props = defineProps({
})
const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`

View File

@@ -26,6 +26,7 @@ const props = defineProps({
})
const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`

View File

@@ -11,6 +11,7 @@ const props = defineProps({
})
const appConfig = useAppConfig()
const colorMode = useColorMode()
const src = computed(() => `https://volta.net/embed/${props.token}?gray=${appConfig.ui.gray}&primary=${appConfig.ui.primary}`)
const src = computed(() => `https://volta.net/embed/${props.token}?theme=${colorMode.value}&gray=${appConfig.ui.gray}&primary=${appConfig.ui.primary}`)
</script>

View File

@@ -0,0 +1,7 @@
<script setup>
const selected = ref(true)
</script>
<template>
<UCheckbox v-model="selected" name="notifications" label="Notifications" />
</template>

View File

@@ -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>

View 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>

View File

@@ -8,11 +8,15 @@ const items = [
}], [{
label: 'Edit',
icon: 'i-heroicons-pencil-square-20-solid',
shortcuts: ['E']
shortcuts: ['E'],
click: () => {
console.log('Edit')
}
}, {
label: 'Duplicate',
icon: 'i-heroicons-document-duplicate-20-solid',
shortcuts: ['D']
shortcuts: ['D'],
disabled: true
}], [{
label: 'Archive',
icon: 'i-heroicons-archive-box-20-solid'

View 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>

View File

@@ -0,0 +1,7 @@
<script setup>
const value = ref('')
</script>
<template>
<UInput v-model="value" />
</template>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -0,0 +1,7 @@
<script setup>
const value = ref(50)
</script>
<template>
<URange v-model="value" />
</template>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -7,8 +7,23 @@ const isOpen = ref(false)
<UButton label="Open" @click="isOpen = true" />
<USlideover v-model="isOpen">
<div class="p-4 h-full">
<Placeholder class="w-full h-full" />
<div class="p-4 sm:p-6 flex flex-col flex-1 gap-4 sm:gap-6">
<div class="flex items-center justify-between">
<h2 class="font-semibold text-gray-900 dark:text-white">
Title
</h2>
<UButton
icon="i-heroicons-x-mark-20-solid"
color="gray"
variant="link"
size="md"
:padded="false"
@click="isOpen = false"
/>
</div>
<Placeholder class="flex-1 w-full" />
</div>
</USlideover>
</div>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,7 @@
<script setup>
const value = ref('')
</script>
<template>
<UTextarea v-model="value" />
</template>

View File

@@ -0,0 +1,7 @@
<script setup>
const selected = ref(false)
</script>
<template>
<UToggle v-model="selected" />
</template>

View File

@@ -3,7 +3,8 @@ const links = [{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
},
badge: 100
}, {
label: 'Installation',
icon: 'i-heroicons-home',

View 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>

View File

@@ -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="{

View File

@@ -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',

View 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>

View 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>

View File

@@ -0,0 +1,37 @@
<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>

View File

@@ -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>

View File

@@ -1,21 +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 flex items-center gap-2',
padding: 'pl-4',
base: 'group text-sm block border-l -ml-px lg:leading-6',
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
rounded: '',
font: '',
ring: '',
active: 'text-primary-500 dark:text-primary-400 border-current',
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
}"
/>
>
<template #badge="{ link }">
<UBadge v-if="link.badge" size="xs" :ui="{ rounded: 'rounded-full' }">
{{ link.badge }}
</UBadge>
</template>
</UVerticalNavigation>
</div>
</div>
</template>
@@ -23,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>

View File

@@ -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-white w-14 h-auto" />
<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>

View File

@@ -13,5 +13,10 @@
</template>
<script setup lang="ts">
const { page } = useContent()
defineProps({
page: {
type: Object,
default: null
}
})
</script>

View File

@@ -1,14 +1,14 @@
<template>
<header v-if="page" class="relative border-b border-gray-200 dark:border-gray-800 pb-8 mb-12">
<p class="mb-4 text-sm leading-6 font-semibold text-primary-500 dark:text-primary-400 capitalize">
{{ useLowerCase(page._dir) }}
{{ page._dir?.title ? page._dir.title : useLowerCase(page._dir) }}
</p>
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
<h1 class="text-3xl sm:text-4xl font-extrabold text-gray-900 tracking-tight dark:text-white">
{{ page.title }}
</h1>
<div 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>

View File

@@ -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" />
<span v-else>&nbsp;</span>
<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">&nbsp;</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>

View File

@@ -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>

View File

@@ -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()

View 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>

View File

@@ -13,7 +13,12 @@
</template>
<script setup lang="ts">
const { toc } = useContent()
defineProps({
toc: {
type: Object,
default: null
}
})
const isTocOpen = ref(false)
</script>

View File

@@ -10,7 +10,16 @@ export async function fetchComponentMeta (name: string) {
if (state.value[name]) { return state.value[name] }
// Store promise to avoid multiple calls
state.value[name] = $fetch(`/api/component-meta/${name}`).then((meta) => {
// add to nitro prerender
if (process.server) {
const event = useRequestEvent()
event.node.res.setHeader(
'x-nitro-prerender',
[event.node.res.getHeader('x-nitro-prerender'), `/api/component-meta/${name}.json`].filter(Boolean).join(',')
)
}
state.value[name] = $fetch(`/api/component-meta/${name}.json`).then((meta) => {
state.value[name] = meta
})

View File

@@ -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

View File

@@ -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

View File

@@ -19,21 +19,77 @@ export default defineAppConfig({
})
```
::alert{icon="i-heroicons-light-bulb"}
Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i-heroicons-swatch-20-solid" class="w-4 h-4 align-middle text-primary-500 dark:text-primary-400"} button in the header.
::
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
To provide dynamic colors that can be changed at runtime, this module uses CSS variables. As Tailwind CSS already has a `gray` color, the module automatically renames it to `cool` to avoid conflicts (`coolGray` was renamed to `gray` when Tailwind CSS v3.0 was released).
Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it would conflict with the `primary` color defined by the module.
::alert{icon="i-heroicons-light-bulb"}
Try to change the `primary` and `gray` colors in the navbar and see the colors change live.
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), [Badge](/elements/badge) and [Button](/elements/button) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
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
@@ -160,6 +216,7 @@ export default defineAppConfig({
},
select: {
default: {
loadingIcon: 'i-octicon-sync-24',
trailingIcon: 'i-octicon-chevron-down-24'
}
},
@@ -170,7 +227,7 @@ export default defineAppConfig({
},
notification: {
default: {
close: {
closeButton: {
icon: 'i-octicon-x-24'
}
}
@@ -178,11 +235,37 @@ export default defineAppConfig({
commandPalette: {
default: {
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'
}
}
}
}
})

View File

@@ -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`

View 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
::

View File

@@ -0,0 +1 @@
title: Getting Started

View File

@@ -29,14 +29,17 @@ baseProps:
### Chip
Use the `chipColor`, `chipVariant` and `chipPosition` 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'
chipVariant: 'solid'
chipText: ''
chipPosition: 'top-right'
size : 'sm'
extraColors:
- gray
baseProps:
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
alt: 'Avatar'
@@ -53,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'
---
::

View File

@@ -113,7 +113,7 @@ Button
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 `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position.
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
::component-card
---
@@ -163,7 +163,7 @@ Button
Use the `loading` prop to show a loading icon and disable the Button.
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.button.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
Use the `loading-icon` prop to set a different icon or change it globally in `ui.button.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
::component-card
---
@@ -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

View File

@@ -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
@@ -24,11 +35,15 @@ const items = [
}], [{
label: 'Edit',
icon: 'i-heroicons-pencil-square-20-solid',
shortcuts: ['E']
shortcuts: ['E'],
click: () => {
console.log('Edit')
}
}, {
label: 'Duplicate',
icon: 'i-heroicons-document-duplicate-20-solid',
shortcuts: ['D']
shortcuts: ['D'],
disabled: true
}], [{
label: 'Archive',
icon: 'i-heroicons-archive-box-20-solid'
@@ -51,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

View File

@@ -5,10 +5,68 @@ description: Display an input field.
## Usage
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
Use the `color` and `variant` props to change the visual style of the Input.
::component-card
---
baseProps:
name: 'input'
placeholder: 'Search...'
props:
color: 'primary'
variant: 'outline'
---
::
Besides all the colors from the `ui.colors` object, you can also use the `white` (default) and `gray` colors with their pre-defined variants.
#### White
::component-card
---
baseProps:
name: 'input'
placeholder: 'Search...'
props:
color: 'white'
variant: 'outline'
excludedProps:
- color
---
::
#### Gray
::component-card
---
baseProps:
name: 'input'
placeholder: 'Search...'
props:
color: 'gray'
variant: 'outline'
excludedProps:
- color
---
::
@@ -38,25 +96,11 @@ props:
---
::
### Appearance
Use the `appearance` prop to change the style of the Input.
::component-card
---
baseProps:
name: 'input'
placeholder: 'Search...'
props:
appearance: 'white'
---
::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position.
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
::component-card
---
@@ -65,9 +109,12 @@ baseProps:
placeholder: 'Search...'
props:
icon: 'i-heroicons-magnifying-glass-20-solid'
appearance: 'white'
size: 'sm'
color: 'white'
trailing: false
extraColors:
- white
- gray
excludedProps:
- icon
---
@@ -81,12 +128,9 @@ Use the `disabled` prop to disable the Input.
---
baseProps:
name: 'input'
props:
placeholder: 'Search...'
appearance: 'white'
props:
disabled: true
excludedProps:
- placeholder
---
::
@@ -94,7 +138,7 @@ excludedProps:
Use the `loading` prop to show a loading icon and disable the Input.
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
Use the `loading-icon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
::component-card
---
@@ -109,30 +153,73 @@ excludedProps:
---
::
### Group
## Slots
You can use the `InputGroup` component to add a label and additional informations to a form element.
### `leading`
::component-card{slug="InputGroup"}
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: 'group'
props:
label: 'Email'
help: "We'll only use this for spam."
hint: 'Required'
required: true
code: >-
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
name: 'input'
placeholder: 'Search...'
---
#default
:u-input{name="group" placeholder="you@example.com" icon="i-heroicons-envelope"}
#leading
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs"}
::
::alert{icon="i-heroicons-light-bulb"}
This also works with `Textarea`, `Select` and `SelectMenu` components.
### `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

View File

@@ -5,10 +5,68 @@ description: Display a textarea field.
## Usage
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
Use the `color` and `variant` props to change the visual style of the Textarea.
::component-card
---
baseProps:
name: 'textarea'
placeholder: 'Search...'
props:
color: 'primary'
variant: 'outline'
---
::
Besides all the colors from the `ui.colors` object, you can also use the `white` (default) and `gray` colors with their pre-defined variants.
#### White
::component-card
---
baseProps:
name: 'textarea'
placeholder: 'Search...'
props:
color: 'white'
variant: 'outline'
excludedProps:
- color
---
::
#### Gray
::component-card
---
baseProps:
name: 'textarea'
placeholder: 'Search...'
props:
color: 'gray'
variant: 'outline'
excludedProps:
- color
---
::
@@ -38,17 +96,17 @@ props:
---
::
### Appearance
### Rows
Use the `appearance` prop to change the style of the Textarea.
Use the `rows` prop to set the number of rows of the Textarea.
::component-card
---
baseProps:
name: 'textarea'
name: 'input'
placeholder: 'Search...'
props:
appearance: 'white'
rows: 1
---
::
@@ -62,11 +120,39 @@ baseProps:
name: 'input'
placeholder: 'Search...'
props:
appearance: 'white'
disabled: true
---
::
### 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

View File

@@ -7,24 +7,114 @@ 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.
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
Use the `color` and `variant` props to change the visual style of the Select.
::component-card
---
baseProps:
name: 'select'
modelValue: 'United States'
props:
options:
- 'United States'
- 'Canada'
- 'Mexico'
props:
color: 'primary'
variant: 'outline'
---
::
Besides all the colors from the `ui.colors` object, you can also use the `white` (default) and `gray` colors with their pre-defined variants.
#### White
::component-card
---
baseProps:
name: 'select'
options:
- 'United States'
- 'Canada'
- 'Mexico'
props:
color: 'white'
variant: 'outline'
excludedProps:
- options
- color
---
::
#### Gray
::component-card
---
baseProps:
name: 'select'
options:
- 'United States'
- 'Canada'
- 'Mexico'
props:
color: 'gray'
variant: 'outline'
excludedProps:
- color
---
::
### Size
Use the `size` prop to change the size of the Input.
Use the `size` prop to change the size of the Select.
::component-card
---
@@ -56,29 +146,11 @@ props:
---
::
### Appearance
Use the `appearance` prop to change the style of the Select.
::component-card
---
baseProps:
name: 'select'
options:
- 'United States'
- 'Canada'
- 'Mexico'
placeholder: 'Search...'
props:
appearance: 'white'
---
::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
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`.
::component-card
---
@@ -91,8 +163,11 @@ baseProps:
placeholder: 'Search...'
props:
icon: 'i-heroicons-magnifying-glass-20-solid'
appearance: 'white'
color: 'white'
size: 'sm'
extraColors:
- white
- gray
excludedProps:
- icon
---
@@ -100,7 +175,7 @@ excludedProps:
### Disabled
Use the `disabled` prop to disable the Input.
Use the `disabled` prop to disable the Select.
::component-card
---
@@ -112,11 +187,73 @@ baseProps:
- 'Mexico'
placeholder: 'Search...'
props:
appearance: 'white'
disabled: true
---
::
### 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

View File

@@ -8,7 +8,7 @@ headlessui:
## Usage
The SelectMenu component renders by default a [Select](/forms/select) component and is based on the `ui.select` preset. You can use most of the Select props to configure the display if you don't want to override the default slot such as [size](/forms/select#size), [placeholder](/forms/select#placeholder), [appearance](/forms/select#appearance), [icon](/forms/select#icon), [disabled](/forms/select#disabled), etc.
The SelectMenu component renders by default a [Select](/forms/select) component and is based on the `ui.select` preset. You can use most of the Select props to configure the display if you don't want to override the default slot such as [color](/forms/select#style), [variant](/forms/select#style), [size](/forms/select#size), [placeholder](/forms/select#placeholder), [icon](/forms/select#icon), [disabled](/forms/select#disabled), etc.
Like the Select component, you can use the `options` prop to pass an array of strings or objects.
@@ -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,49 +41,20 @@ 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.
### Objects
::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>
```
::
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `optionAttribute` prop that defaults to `label`.
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`.
::component-example
#default
@@ -134,11 +107,7 @@ 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 `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
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,22 @@ 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'
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.
Use the `searchable-placeholder` prop to set a different placeholder.
This will use Headless UI [Combobox](https://headlessui.com/vue/combobox) component instead of [Listbox](https://headlessui.com/vue/listbox).
::component-card
@@ -167,9 +142,67 @@ baseProps:
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
props:
searchable: true
searchablePlaceholder: 'Search a person...'
---
::
## 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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -0,0 +1,141 @@
---
github:
suffix: .ts
description: Display a label and additional informations around a form element.
---
## Usage
Use the FormGroup component around an [Input](/forms/input), [Textarea](/forms/textarea), [Select](/forms/select) or a [SelectMenu](/forms/select-menu) with the `name` prop to automatically associate a `<label>` element with the form element.
::component-card
---
props:
name: 'email'
label: 'Email'
code: >-
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
---
#default
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
::
### Required
Use the `required` prop to indicate that the form element is required.
::component-card
---
baseProps:
name: 'group-required'
props:
label: 'Email'
required: true
code: >-
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
---
#default
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
::
### Description
Use the `description` prop to display a description below the label.
::component-card
---
baseProps:
name: 'group-description'
props:
label: 'Email'
description: "We'll only use this for spam."
code: >-
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
---
#default
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
::
### Hint
Use the `hint` prop to display a hint above the form element.
::component-card
---
baseProps:
name: 'group-hint'
props:
label: 'Email'
hint: 'Optional'
code: >-
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
---
#default
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
::
### Help
Use the `help` prop to display an help message below the form element.
::component-card
---
baseProps:
name: 'group-help'
props:
label: 'Email'
help: 'We will never share your email with anyone else.'
code: >-
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
---
#default
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
::
### Error
Use the `error` prop to display an error message below the form element.
When used together with the `help` prop, the `error` prop will take precedence.
::component-card
---
baseProps:
name: 'group-error'
props:
label: 'Email'
help: 'We will never share your email with anyone else.'
error: "Not a valid email address."
code: >-
<UInput placeholder="you@example.com" trailing-icon="i-heroicons-exclamation-triangle-20-solid" />
---
#default
:u-input{model-value="benjamincanac" placeholder="you@example.com" trailing-icon="i-heroicons-exclamation-triangle-20-solid"}
::
You can also use the `error` prop as a boolean to mark the form element as invalid.
::alert{icon="i-heroicons-light-bulb"}
The `error` prop will automatically set the `color` prop of the form element to `red`.
::
## Props
:component-props
## Preset
:component-preset

View 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

View File

@@ -16,7 +16,8 @@ const links = [{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
},
badge: 100
}, {
label: 'Installation',
icon: 'i-heroicons-home',

View File

@@ -160,13 +160,13 @@ function onSelect (option) {
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.commandPalette.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
Use the `selected-icon` prop to set a different icon or change it globally in `ui.commandPalette.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
::component-card
---
padding: false
baseProps:
empty: null
emptyState: null
props:
icon: 'i-heroicons-command-line'
excludedProps:
@@ -174,6 +174,24 @@ excludedProps:
---
::
### Loading
Use the `loading` prop to show a loading icon.
Use the `loading-icon` prop to set a different icon or change it globally in `ui.commandPalette.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
::component-card
---
padding: false
baseProps:
emptyState: null
props:
loading: true
excludedProps:
- icon
---
::
### Placeholder
Use the `placeholder` prop to change the input placeholder
@@ -182,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:
@@ -192,31 +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 state.
::component-card
---
@@ -224,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
---
::
@@ -237,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 `commandAttribute` 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.
@@ -299,38 +319,42 @@ const groups = computed(() => {
```
::
## Themes
::alert{icon="i-heroicons-light-bulb"}
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`.
::
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
## Slots
### Algolia
### `empty-state` :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full"}
::component-example
Use the `#empty-state` slot to customize the empty state.
::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

View 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

View File

@@ -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.'
---
::
@@ -92,8 +132,8 @@ baseProps:
id: 4
timeout: 0
title: 'Notification'
description: 'This is a notification.'
props:
description: 'This is a notification.'
avatar:
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
excludedProps:
@@ -114,7 +154,28 @@ baseProps:
title: 'Notification'
description: 'This is a notification.'
props:
timeout: 10000
timeout: 60000
---
::
### Style
Use the `color` prop to change the progress and icon color of the Notification.
::component-card
---
baseProps:
id: 6
title: 'Notification'
description: 'This is a notification.'
timeout: 600000
props:
icon: 'i-heroicons-check-badge'
color: 'primary'
extraColors:
- gray
excludedProps:
- icon
---
::
@@ -166,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'
@@ -186,7 +247,7 @@ props:
ui:
rounded: 'rounded-full'
excludedProps:
- close
- closeButton
---
::
@@ -209,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:
@@ -235,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

Some files were not shown because too many files have changed in this diff Show More