Compare commits

..

511 Commits

Author SHA1 Message Date
Benjamin Canac
3c5256c462 chore(release): 2.0.0 2023-05-04 14:56:33 +02:00
Benjamin Canac
a38ef00fb8 chore(package): volta pin node 18 2023-05-04 14:55:18 +02:00
Benjamin Canac
6da0db0113 feat: rewrite to use app config and rework docs (#143)
Co-authored-by: Daniel Roe <daniel@roe.dev>
Co-authored-by: Sébastien Chopin <seb@nuxt.com>
2023-05-04 14:49:19 +02:00
Benjamin Canac
56230ea915 chore(release): 1.2.11 2023-05-04 11:01:28 +02:00
Benjamin Canac
126b5fcfd4 Revert "chore(github): add v1 branch to ci dev"
This reverts commit d3536d8768.
2023-05-04 11:00:12 +02:00
Benjamin Canac
d3536d8768 chore(github): add v1 branch to ci dev 2023-05-04 10:52:54 +02:00
Sylvain Marroufin
59f62d322f fix(defineShortcuts): use useEventListener (#150) 2023-05-04 10:50:10 +02:00
Sylvain Marroufin
b85a8e7203 chore(defineShortcuts): config prop whenever more flexible (#149) 2023-05-02 14:47:31 +02:00
Benjamin Canac
8830d848fd chore(release): 1.2.10 2023-04-07 19:23:36 +02:00
Benjamin Canac
cfce1524b2 fix(CommandPalette): typecheck 2023-04-07 19:23:25 +02:00
Benjamin Canac
f845e89a76 chore(release): 1.2.9 2023-04-07 19:21:55 +02:00
Benjamin Canac
d9ca5d188a chore(CommandPalette): improve command highlight 2023-04-07 18:30:25 +02:00
Benjamin Canac
2429bcf5a7 chore(SelectCustom): right padding only when selected 2023-04-05 13:01:16 +02:00
Benjamin Canac
f45f4a3e56 chore(release): 1.2.8 2023-04-04 15:14:06 +02:00
Benjamin Canac
09e957e702 chore(deps): remove @tailwindcss/line-clamp as its included by default in tailwind 3.3 2023-04-04 13:55:38 +02:00
Benjamin Canac
1ecd7cefde chore(release): 1.2.7 2023-04-04 13:36:43 +02:00
Benjamin Canac
aafdfdb59c fix(useTimer): remaining after pause 2023-04-04 13:36:24 +02:00
Benjamin Canac
453ff6ca20 chore(release): 1.2.6 2023-04-04 11:26:00 +02:00
Benjamin Canac
55832b6b99 docs: ts ignore 2023-04-04 11:25:33 +02:00
Benjamin Canac
6b93bbe5cd chore(release): 1.2.5 2023-04-04 11:17:30 +02:00
Benjamin Canac
1402553145 chore(deps): bump 2023-04-04 11:17:14 +02:00
Benjamin Canac
5d84dfd05b chore(release): 1.2.4 2023-04-04 11:08:58 +02:00
Benjamin Canac
7dc59a05ec chore(useTimer): pass options to useTimestamp 2023-04-04 11:08:41 +02:00
Benjamin Canac
4bd994985d chore(release): 1.2.3 2023-03-22 16:21:49 +01:00
Benjamin Canac
c83d3b7147 chore(Avatar): remove useless chipVariant prop 2023-03-22 16:21:29 +01:00
Benjamin Canac
f022665351 chore(release): 1.2.2 2023-03-20 16:17:55 +01:00
Benjamin Canac
f29c325dc7 chore(deps): fix @headlessui/vue to 1.7.10 because of inert dialogs 2023-03-20 16:17:36 +01:00
Benjamin Canac
876f9578c2 chore(release): 1.2.1 2023-03-20 15:59:15 +01:00
Benjamin Canac
f69f584188 chore(deps): bump 2023-03-20 15:58:53 +01:00
Sylvain Marroufin
377b4189ca fix(defineShortcuts): shift + alphabetic character handling (#140) 2023-03-13 14:11:52 +01:00
Benjamin Canac
f76a9f0ab0 chore(release): 1.2.0 2023-03-09 16:55:06 +01:00
Sylvain Marroufin
37b2271bf0 fix(defineShortcuts): add missing import 2023-03-09 16:09:31 +01:00
Sylvain Marroufin
fa49d52f17 fix(Tooltip): shortcutsClass prop type 2023-03-09 15:37:42 +01:00
Sylvain Marroufin
fd4b608150 chore: add more composables (#138) 2023-03-09 11:42:22 +01:00
Sylvain Marroufin
fef93f3198 chore: allow preset override of components shortcuts (#139)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2023-03-09 11:40:04 +01:00
Benjamin Canac
0826ef8d59 chore(deps): bump 2023-03-03 16:51:41 +01:00
Benjamin Canac
0e3066d865 chore(release): 1.1.4 2023-03-02 12:37:17 +01:00
Benjamin Canac
fb9d6cb544 chore(deps): freeze @headlessui/vue to 1.7.10 2023-03-02 12:36:55 +01:00
Benjamin Canac
531a89cdb8 chore(release): 1.1.3 2023-03-02 11:02:04 +01:00
Benjamin Canac
6970c2d665 chore(CommandPalette): add slot for command icon 2023-02-28 16:49:50 +01:00
Benjamin Canac
9719ea3858 chore(release): 1.1.2 2023-02-28 11:58:28 +01:00
Benjamin Canac
a4af6b3805 chore(deps): bump 2023-02-28 10:50:50 +01:00
Benjamin Canac
3493c138d9 chore: split props for Dropdown, ContextMenu and Popover 2023-02-24 17:33:57 +01:00
Benjamin Canac
d08e64d53f fix(Tooltip): truncate 2023-02-22 15:59:02 +01:00
renovate[bot]
6aecb082d2 chore(deps): update devdependency vue-tsc to ^1.1.7 (#132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-22 12:31:21 +01:00
Sylvain Marroufin
63e27f8b4b chore(docs): add ContextMenu component page (#128) 2023-02-22 12:31:08 +01:00
Benjamin Canac
7970aefcb0 fix(VerticalNavigation): links to type 2023-02-22 12:22:54 +01:00
renovate[bot]
a893d7fa2e chore(deps): update all non-major dependencies (#124)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-21 19:26:03 +01:00
Benjamin Canac
8ace629ff8 chore(release): 1.1.1 2023-02-20 18:06:15 +01:00
Benjamin Canac
0d35b82ecb chore(CommandPalette): expose query 2023-02-20 18:05:52 +01:00
Benjamin Canac
5f37077835 types(CommandPalette): options no longer exists 2023-02-20 18:05:39 +01:00
Benjamin Canac
948f4b89b1 chore(release): 1.1.0 2023-02-17 19:08:09 +01:00
Benjamin Canac
e6d0dd5898 chore(CommandPalette): set debounce to 200 2023-02-17 19:07:38 +01:00
Benjamin Canac
4702a4f103 fix(CommandPalette): types 2023-02-17 18:14:11 +01:00
Benjamin Canac
efa9674815 feat(CommandPalette): handle async search for specific groups 2023-02-17 18:03:59 +01:00
Benjamin Canac
97d40395d3 chore(release): 1.0.0 2023-02-17 15:35:51 +01:00
Benjamin Canac
a2fb22d835 docs: ts-ignore module import 2023-02-17 14:53:35 +01:00
Benjamin Canac
d14a1a82c2 chore(deps): bump @egoist/tailwindcss-icons 2023-02-17 14:50:41 +01:00
Benjamin Canac
a566627a23 chore: bump deps 2023-02-17 14:26:20 +01:00
Benjamin Canac
ee3352278c feat: migrate to @egoist/tailwindcss-icons 2023-02-17 12:58:19 +01:00
Benjamin Canac
b1d9e01818 chore(release): 0.2.1 2023-02-16 17:02:23 +01:00
Benjamin Canac
ca171f3095 chore(ci): disable typecheck 2023-02-16 17:02:07 +01:00
Benjamin Canac
c0e493d96a chore(deps): bump nuxt-icon 2023-02-16 17:02:00 +01:00
Benjamin Canac
d0d3235860 chore(release): 0.2.0 2023-02-16 16:17:24 +01:00
Benjamin Canac
18915975be chore(release): 0.1.39 2023-02-16 16:16:57 +01:00
Benjamin Canac
f5d068be9d feat: use nuxt-icon 2023-02-16 16:16:37 +01:00
Benjamin Canac
6018f009a8 fix(SelectCustom): handle search on string arrays 2023-02-16 12:02:38 +01:00
Benjamin Canac
2b78b5d7dc chore(SelectCustom): improve options type 2023-02-16 11:53:19 +01:00
Benjamin Canac
87f3f0b4c0 chore(release): 0.1.38 2023-02-03 18:09:49 +01:00
Benjamin Canac
41bf56f2ae chore(tsconfig): remove override 2023-02-03 18:04:30 +01:00
Benjamin Canac
b7795f4ef6 chore(deps): add @types/node 2023-02-03 18:04:19 +01:00
Benjamin Canac
57f8145a8d chore(deps): bump 2023-02-03 17:51:06 +01:00
Benjamin Canac
70fbcb6b24 chore(release): 0.1.37 2023-02-03 16:02:21 +01:00
Kevin Marrec
bea47b5906 fix(CommandPalette): improve accessibility (#129) 2023-02-03 16:01:43 +01:00
Benjamin Canac
fc1b3b2f17 chore(release): 0.1.36 2023-02-02 15:10:25 +01:00
Benjamin Canac
5bf5a314c4 fix(CommandPalette): put back cursor on top only when query changes 2023-02-01 15:20:21 +01:00
Benjamin Canac
3558eb1a4f chore(release): 0.1.35 2023-02-01 14:38:15 +01:00
Benjamin Canac
1c4d46e056 fix(Dropdown): lint 2023-02-01 14:38:07 +01:00
Benjamin Canac
1b0ed9e732 docs: simplify popover panel template 2023-02-01 14:37:55 +01:00
Benjamin Canac
b72037a777 chore: handle disabled prop for Dropdown and Popover 2023-02-01 14:34:28 +01:00
Benjamin Canac
a7644860b8 fix(Dropdown): prevent panel display when no items 2023-01-31 12:33:27 +01:00
Benjamin Canac
c90cd9c4f3 fix(AvatarGroup): preset size prop 2023-01-28 01:49:24 +01:00
Benjamin Canac
7805168685 chore(release): 0.1.34 2023-01-27 14:10:46 +01:00
Benjamin Canac
27717a55b3 fix(CommandPalette): typecheck 2023-01-27 14:10:35 +01:00
Benjamin Canac
d651a22dce chore(release): 0.1.33 2023-01-27 13:02:18 +01:00
Benjamin Canac
c3ecbf4b20 chore(CommandPalette): start preset 2023-01-27 13:02:01 +01:00
Benjamin Canac
d8b10f3eef chore(release): 0.1.32 2023-01-23 14:24:36 +01:00
Benjamin Canac
1071b80b39 chore(deps): bump 2023-01-23 14:24:20 +01:00
Benjamin Canac
c5e9a1ef46 chore(release): 0.1.31 2023-01-17 15:35:38 +01:00
Benjamin Canac
afe69a570d chore(deps): bump 2023-01-17 15:35:16 +01:00
Benjamin Canac
e6ed834cea chore(release): 0.1.30 2023-01-17 15:00:54 +01:00
Benjamin Canac
30c5412a6b chore(CommandPalette): input close icon position 2023-01-17 15:00:21 +01:00
Benjamin Canac
01f56d9553 chore(Button): handle compact 2023-01-17 15:00:21 +01:00
Benjamin Canac
91f273c117 chore(release): 0.1.29 2023-01-17 15:00:19 +01:00
Benjamin Canac
cda8ce32a3 chore(release): 0.1.28 2023-01-13 18:33:18 +01:00
Benjamin Canac
2bc0eb05d1 chore(Slideover): emit event 2023-01-13 18:32:45 +01:00
Benjamin Canac
cfc4bdfbfe chore(release): 0.1.27 2023-01-12 15:33:35 +01:00
Sylvain Marroufin
370d05921d chore(lighthouse): improve CommandPalette 2023-01-12 14:28:29 +01:00
Sylvain Marroufin
b6455a151d chore(lighthouse): improve components accessibility (#127) 2023-01-12 12:32:42 +01:00
Benjamin Canac
8c0e0ec823 chore(release): 0.1.26 2023-01-09 18:43:45 +01:00
Sylvain Marroufin
4f56921096 fix(CommandPalette): select first item on search changes (#126) 2023-01-09 18:43:12 +01:00
Benjamin Canac
6a5ee32e05 chore(release): 0.1.25 2023-01-09 18:42:53 +01:00
Benjamin Canac
4ea07e1077 chore(release): 0.1.24 2023-01-04 12:17:13 +01:00
Benjamin Canac
5fd65d0917 chore(deps): bump 2023-01-04 11:39:24 +01:00
Benjamin Canac
2ec0cee1d9 chore(release): 0.1.23 2022-12-20 16:27:30 +01:00
Benjamin Canac
758e6f1400 Revert "chore: put back stop propagation on mode hover (#121)"
This reverts commit c015148f29.
2022-12-20 16:26:55 +01:00
Benjamin Canac
275fa1831d chore(release): 0.1.22 2022-12-19 18:27:36 +01:00
Benjamin Canac
8b5e08f6f2 chore(Tooltip): add prevent prop 2022-12-19 18:27:19 +01:00
Benjamin Canac
1635f57de6 chore(release): 0.1.21 2022-12-19 16:22:41 +01:00
Sylvain Marroufin
b3e0122001 chore(SelectCustom): add emit on open/close (#125) 2022-12-19 16:22:15 +01:00
Benjamin Canac
4f9d20e603 chore(release): 0.1.20 2022-12-19 11:37:46 +01:00
Benjamin Canac
f7add47cf2 chore(deps): pin @headlessui/vue version to 1.7.4 2022-12-19 11:37:28 +01:00
renovate[bot]
99c1c683eb Update all non-major dependencies (#122)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-17 16:57:12 +01:00
Anthony Fu
ff9f6c251d fix: avoid referring to complex types in props (#123) 2022-12-17 16:56:49 +01:00
Benjamin Canac
e0c703ca6c chore(release): 0.1.19 2022-12-16 10:47:39 +01:00
Benjamin Canac
2210faa160 chore(deps): bump @headlessui/vue 2022-12-16 10:47:20 +01:00
Benjamin Canac
19589b5e05 chore(release): 0.1.18 2022-12-15 14:45:48 +01:00
Benjamin Canac
7051fa39a7 chore: add missing w-full on headlessui buttons 2022-12-15 14:45:14 +01:00
renovate[bot]
911278e95e chore(deps): update all non-major dependencies (#120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-13 13:55:28 +01:00
Sylvain Marroufin
c015148f29 chore: put back stop propagation on mode hover (#121) 2022-12-13 13:54:23 +01:00
Benjamin Canac
cb1fd55801 chore(release): 0.1.17 2022-12-06 16:56:31 +01:00
Benjamin Canac
16fd1c0ca3 fix: remove stop propagation on mode hover 2022-12-06 16:56:16 +01:00
Benjamin Canac
84ac92ed7a chore(release): 0.1.16 2022-12-06 16:25:01 +01:00
Benjamin Canac
0ade69de26 fix(Popover): preset from tooltip 2022-12-06 16:24:33 +01:00
Benjamin Canac
d9193abf55 chore(release): 0.1.15 2022-12-02 17:16:52 +01:00
Sylvain Marroufin
44a78d7f67 fix(Dropdown): better handle item click to preventDefault (#119) 2022-12-02 17:16:32 +01:00
Benjamin Canac
3ce600f89a chore(release): 0.1.14 2022-12-02 17:16:00 +01:00
Benjamin Canac
662dd9ee65 chore(release): 0.1.13 2022-12-01 14:08:26 +01:00
renovate[bot]
3be3751bfc chore(deps): update all non-major dependencies (#117)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-01 13:02:31 +01:00
Sylvain Marroufin
5042b5806b chore(components): update props with optional fields (#118) 2022-12-01 13:01:45 +01:00
Benjamin Canac
2535301bcd chore(release): 0.1.12 2022-11-29 13:56:30 +01:00
Benjamin Canac
629bb72424 fix(Checkbox): types 2022-11-29 13:54:35 +01:00
Benjamin Canac
543b4e025f chore(release): 0.1.11 2022-11-29 12:56:04 +01:00
Benjamin Canac
7f18c6bdc7 fix(Checkbox): revert type fix as it breaks checkboxes 2022-11-29 12:55:44 +01:00
Benjamin Canac
0ea924ca0d chore(release): 0.1.10 2022-11-26 23:44:03 +01:00
renovate[bot]
22f3d42470 chore(deps): update dependency @nuxtjs/color-mode to ^3.2.0 (#63)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-26 23:43:10 +01:00
Benjamin Canac
1ad96065fd fix: default popper options 2022-11-26 23:42:36 +01:00
Anthony Fu
952786ed79 fix: default props for command palette (#116) 2022-11-26 23:41:25 +01:00
Benjamin Canac
9cf92e34ab chore(release): 0.1.9 2022-11-25 10:00:40 +01:00
Benjamin Canac
6321d3ed8d fix(Icon): couldn't load anymore 2022-11-25 10:00:30 +01:00
Benjamin Canac
bc0c168c0b fix(Icon): eslint ignore 2022-11-23 17:00:08 +01:00
Benjamin Canac
9975305f2a chore(deps): bump 2022-11-23 11:32:08 +01:00
Anthony Fu
edc1bd677b chore: improve types (#115)
* wip: improve types

* feat: improve types

* Apply suggestions from code review

Co-authored-by: Sylvain Marroufin <marroufin.sylvain@gmail.com>

* chore: update

* chore: enable ci typecheck

* chore: fix

Co-authored-by: Sylvain Marroufin <marroufin.sylvain@gmail.com>
2022-11-23 11:30:18 +01:00
Benjamin Canac
32d1f21299 chore(release): 0.1.8 2022-11-16 18:01:05 +01:00
Benjamin Canac
f901e417e1 chore(deps): bump nuxt to 3.0.0 2022-11-16 16:09:50 +01:00
Benjamin Canac
23921e1ae2 chore(release): 0.1.7 2022-11-16 10:57:06 +01:00
Benjamin Canac
8a5f52dbc8 chore(deps): bump 2022-11-16 10:56:50 +01:00
Benjamin Canac
04d5ce1c51 chore(release): 0.1.6 2022-11-15 12:08:10 +01:00
Benjamin Canac
3a300f72c1 fix(SelectCustom): add w-full on ComboboxButton 2022-11-10 14:37:37 +01:00
Benjamin Canac
927debfb6b chore(SelectCustom): add missing inline-flex on button 2022-11-10 13:54:43 +01:00
Benjamin Canac
44176ee897 chore(Notification): improve actions text color 2022-11-09 11:52:52 +01:00
Benjamin Canac
7f9d69183c chore(release): 0.1.5 2022-11-08 16:44:13 +01:00
Benjamin Canac
23e5f4c501 chore(CommandPalette): add autoclear prop 2022-11-08 16:43:18 +01:00
Benjamin Canac
24998e3ac5 chore(release): 0.1.4 2022-11-08 11:02:42 +01:00
Benjamin Canac
bebf18a89a chore(deps): bump 2022-11-08 11:02:22 +01:00
Anthony Fu
1b56b50d4d fix(button): support RouteLocationRaw type for to (#112) 2022-11-08 10:57:55 +01:00
Benjamin Canac
3337559b89 chore(release): 0.1.3 2022-11-04 12:21:08 +01:00
Benjamin Canac
d33a23ebc0 chore(deps): bump 2022-11-04 12:19:20 +01:00
Baptiste Leproux
28586c35a5 fix(types): add missing field in command palette type (#111) 2022-11-02 16:04:50 +01:00
Benjamin Canac
e521e1ac24 fix(docs): component input string field 2022-11-02 11:21:35 +01:00
Sylvain Marroufin
8ea3223071 chore(typescript): minor fixes (#110) 2022-11-02 10:50:40 +01:00
Benjamin Canac
4cbe983f61 chore(release): 0.1.2 2022-10-27 16:05:16 +02:00
Benjamin Canac
e42969f003 chore(Tooltip): improve design 2022-10-27 16:04:49 +02:00
Benjamin Canac
c93e37a0eb chore(Tooltip): handle shortcuts 2022-10-27 14:26:31 +02:00
Sylvain Marroufin
9e20f96b65 chore(Dropdown): don't show outline on keyboard navigation (#108) 2022-10-27 11:52:16 +02:00
Benjamin Canac
9f6f132a76 chore(release): 0.1.1 2022-10-26 13:13:13 +02:00
Sylvain Marroufin
5517cc2846 fix(Dropdown): close on click item with to (#103)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2022-10-26 13:05:41 +02:00
Benjamin Canac
8c24b5dc97 chore(module): add d.ts file 2022-10-26 13:04:36 +02:00
Benjamin Canac
ec8bd5cdc4 fix(SelectCustom): types and lint 2022-10-26 13:04:36 +02:00
Benjamin Canac
6fab68baa8 fix(CommandPalette): lint 2022-10-26 13:04:36 +02:00
Benjamin Canac
fc951e2980 chore(CommandPalette): improve types 2022-10-26 13:04:36 +02:00
Benjamin Canac
08ff6e6c09 docs: update 2022-10-26 13:04:36 +02:00
Sylvain Marroufin
9dcdaf474e chore(Dropdown|Popover|Tooltip): hover delay (#104) 2022-10-26 11:59:49 +02:00
Sylvain Marroufin
e9f0224b91 fix(Popover): avoid crash on mount if ref not loaded (#105) 2022-10-26 11:57:13 +02:00
Benjamin Canac
929192fd46 chore(deps): bump vueuse 2022-10-25 16:32:42 +02:00
Benjamin Canac
d9ff11d1a2 chore(release): 0.1.0 2022-10-25 14:52:45 +02:00
Benjamin Canac
80edb1bd4b chore(deps): bump 2022-10-25 14:11:27 +02:00
Sylvain Marroufin
fc658842bb chore: fix typescript errors (#102) 2022-10-25 14:09:54 +02:00
Benjamin Canac
32922def7d fix(CommandPalette): group items spacing 2022-10-24 16:46:19 +02:00
Benjamin Canac
a4bea1c508 chore(CommandPalette): reduce icon and avatar sizes 2022-10-24 16:19:27 +02:00
Benjamin Canac
f87252f05d fix(CommandPalette): prevent shortcuts to disappear on hover 2022-10-24 16:18:20 +02:00
Benjamin Canac
ec9f67094a fix(CommandPalette): reactivity issue + improve results
Resolves #95, resolves #96
2022-10-24 15:58:15 +02:00
Benjamin Canac
72dc0d0d0c fix(Modal): use object for innerStyle 2022-10-23 19:29:54 +02:00
Benjamin Canac
02b0a29d86 chore(Modal): add innerStyle prop 2022-10-23 19:26:30 +02:00
Benjamin Canac
cd2f1122be chore(Modal): add inner ref 2022-10-23 19:00:36 +02:00
Benjamin Canac
e3943123ce chore(deps): bump 2022-10-21 15:55:25 +02:00
Baptiste Leproux
d57647a77a fix(icon): hydratation warning when loading same icon twice (#99) 2022-10-20 11:34:58 +02:00
Benjamin Canac
aa97f26180 chore(deps): bump 2022-10-19 11:28:40 +02:00
Benjamin Canac
0b17fb02e4 chore(deps): bump 2022-10-18 11:24:11 +02:00
Baptiste Leproux
5039265097 chore: set popper options in preset (#97)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2022-10-18 11:13:28 +02:00
Benjamin Canac
c89c65bd44 chore(Dropdown): allow group preset change 2022-10-13 17:10:02 +02:00
Sylvain Marroufin
4515a1239b chore: expose all types in index.ts (#94) 2022-10-13 12:47:43 +02:00
Sylvain Marroufin
de9596bfc4 chore(Tooltip): update preset for truncate (#90) 2022-10-10 15:44:39 +02:00
Benjamin Canac
7da11ccec0 chore: improve Modal / Slideover preset 2022-10-09 21:35:53 +02:00
Benjamin Canac
d29377f614 chore(CommandPalette): support by prop and highlight 2022-10-09 13:31:11 +02:00
Benjamin Canac
f0e482cf01 chore(SelectCustom): support by prop 2022-10-09 13:30:24 +02:00
Benjamin Canac
056ab30474 fix(CommandPalette): prevent empty active slot 2022-10-09 12:54:45 +02:00
Benjamin Canac
95c14a4360 fix: default object options 2022-10-08 23:40:33 +02:00
Benjamin Canac
b25bb75783 chore(CommandPalette): show placeholder even when searchable 2022-10-08 23:31:35 +02:00
Benjamin Canac
98ae6c14aa docs: improve ContextMenu example 2022-10-08 23:22:23 +02:00
Benjamin Canac
8ee2a3cf5b chore(CommandPalette): expose to take control from textarea 2022-10-08 23:22:06 +02:00
Benjamin Canac
91f93c450e chore(ContextMenu): improve options 2022-10-08 22:24:57 +02:00
Benjamin Canac
237a673af3 chore(popper): add more options 2022-10-08 22:24:42 +02:00
Benjamin Canac
591e60dfc7 chore(deps): bump 2022-10-08 22:24:28 +02:00
Benjamin Canac
b9f0b3cb10 chore(ContextMenu): new component 2022-10-08 00:51:53 +02:00
Benjamin Canac
3ed64250cd chore(deps): bump 2022-10-08 00:37:26 +02:00
Benjamin Canac
f1b59fc59e chore: fix popper props merge 2022-10-07 13:40:23 +02:00
Benjamin Canac
005c18e4c0 chore: improve popper handling 2022-10-07 13:27:51 +02:00
Benjamin Canac
44b7199c4f chore(docs): improve 2022-10-07 13:27:42 +02:00
Sylvain Marroufin
4774adc0ae chore(SelectCustom): clear search on select (#88) 2022-10-06 15:56:20 +02:00
Sylvain Marroufin
03b0e07a53 chore(Tooltip): allow to disable popper flip (#87) 2022-10-06 15:54:28 +02:00
Sylvain Marroufin
2560088b2b chore(CommandPalette): allow to disable autoselect (#84) 2022-10-05 15:10:51 +02:00
Benjamin Canac
4ce991652b chore(Slideover): allow to disable transitions 2022-10-05 00:58:01 +02:00
Benjamin Canac
d96b4ea119 chore(preset): handle Modal / Slideover transitions 2022-10-05 00:24:44 +02:00
Benjamin Canac
b20c8c82ab chore(preset): handle transitions 2022-10-04 23:56:54 +02:00
Benjamin Canac
e5bf052c1f chore(deps): bump @nuxtjs/color-mode 2022-10-04 23:46:13 +02:00
Sylvain Marroufin
cf1b2cdd13 fix(SelectCustom): avoid submitting to form when closing (#83) 2022-10-04 16:48:31 +02:00
Benjamin Canac
8776929f63 chore(deps): bump 2022-10-03 17:50:27 +02:00
Daniel Roe
62361bfa8f fix: load icons on mount rather than within setup (#82) 2022-10-03 17:49:13 +02:00
Clément Ollivier
bf25ad15af chore: replace deprecated addAutoImportDir function (#81) 2022-10-03 10:26:12 +02:00
Benjamin Canac
03b18b1ff8 chore(deps): clean lock 2022-09-22 15:14:04 +02:00
Benjamin Canac
fdf149f0d0 chore(deps): bump 2022-09-22 15:11:09 +02:00
Benjamin Canac
e4c3a74e0e chore(Notification): add preset system 2022-09-20 11:47:52 +02:00
Benjamin Canac
38f248c24d chore(Modal): handle transition disable 2022-09-16 17:43:43 +02:00
Benjamin Canac
74de5b106b chore(Modal): improve preset 2022-09-16 16:57:44 +02:00
Benjamin Canac
70f8cd4889 chore(deps): remove nuxt-icon 2022-09-13 10:56:41 +02:00
Benjamin Canac
6a2159da94 Revert "chore: use nuxt-icon"
This reverts commit 70495e0a22.
2022-09-12 19:15:54 +02:00
Benjamin Canac
ae6b3c0ba6 chore(docs): set font-family like in tailwindui 2022-09-12 19:09:03 +02:00
Benjamin Canac
70495e0a22 chore: use nuxt-icon 2022-09-09 15:24:49 +02:00
Benjamin Canac
be99df3b30 chore(deps): bump 2022-09-09 15:24:49 +02:00
Sébastien Chopin
6870eff661 chore: update docs 2022-09-09 12:57:23 +02:00
Sébastien Chopin
062e468518 chore: remove not needed command 2022-09-09 12:19:16 +02:00
Benjamin Canac
3fc2a043fa chore(presets): handle radio and checkbox dark mode 2022-09-07 16:02:25 +02:00
Benjamin Canac
e99ec9b82b chore(VerticalNavigation): pass is-active to slots 2022-09-07 14:30:03 +02:00
Benjamin Canac
4556f24eb1 chore(deps): use nuxt rc.9 2022-09-05 11:03:48 +02:00
Benjamin Canac
f8fb66f177 chore(deps): upgrade @nuxt/kit 2022-09-05 10:58:35 +02:00
Benjamin Canac
5d760d9d8d chore(deps): bump 2022-09-05 10:54:09 +02:00
Benjamin Canac
cb5c9b3923 chore(package): try something for vercel deploy 2022-09-02 13:01:07 +02:00
Benjamin Canac
8e9418f94f chore(deps): bump @nuxt/kit 2022-09-02 13:00:56 +02:00
Benjamin Canac
4712726fbd docs: fix lint 2022-09-02 11:41:18 +02:00
Benjamin Canac
0cb38e2214 chore: prepare module for nuxt 3.0.0-rc9 (#77) 2022-09-02 11:37:59 +02:00
Sylvain Marroufin
3e9c502fbe chore(AvatarGroup): enhance avatars overlap (#75) 2022-08-29 18:57:11 +02:00
Benjamin Canac
541ed304a0 fix: error in Popover and Dropdown 2022-08-26 15:04:06 +02:00
Benjamin Canac
72919425b6 fix(Dropdown): increase timeout for hover mode 2022-08-17 14:14:13 +02:00
Benjamin Canac
8a66f5e9bf fix(Notification): prevent error without timeout 2022-08-17 14:10:34 +02:00
Benjamin Canac
a0d56d0f66 chore(Notifications): support actions instead of specific undo and dismiss 2022-08-17 13:53:53 +02:00
Benjamin Canac
c6706c76b2 chore(Dropdown): add item.iconClass 2022-08-12 17:19:52 +02:00
Benjamin Canac
1f7f28835d chore(Dropdown): pass event to item click 2022-08-12 17:16:00 +02:00
Benjamin Canac
593573a286 fix(Popover): inline-flex on trigger button 2022-08-12 10:27:13 +02:00
Benjamin Canac
3a6ecd6988 chore(SelectCustom): move icons in preset 2022-07-29 11:17:04 +02:00
Sylvain Marroufin
f9dbe28938 chore(typescript): fix typed imports (#72) 2022-07-25 18:45:37 +02:00
Benjamin Canac
2ae17ed919 chore(tailwind): override outline focus to primary color 2022-07-24 12:07:36 +02:00
Benjamin Canac
abb93b5fd3 fix(CommandPalette): command icons opacity in dark mode 2022-07-23 23:57:50 +02:00
Benjamin Canac
f75fc4f864 chore(Dropdown): support shortcuts 2022-07-23 23:53:13 +02:00
Benjamin Canac
5722a3ae62 fix(CommandPalette): icon inactive opacity on dark mode 2022-07-23 23:39:22 +02:00
Benjamin Canac
aa242aa87d fix(CommandPalette): truncate suffix 2022-07-22 18:06:11 +02:00
Benjamin Canac
945fec62c2 fix(Notification): improve placement with description 2022-07-22 17:07:37 +02:00
Sylvain Marroufin
9302b8d635 fix(CommandPalette): fix groups computed 2022-07-22 16:19:38 +02:00
Benjamin Canac
b43394ddc3 fix(CommandPalette): hack for reactivity 2022-07-22 15:47:32 +02:00
Benjamin Canac
be94fea84a fix: to prop type 2022-07-22 13:07:23 +02:00
Benjamin Canac
c08501db3f chore(VerticalNavigation): correct avatar size by default 2022-07-22 12:16:23 +02:00
Benjamin Canac
db2b73e741 chore(plugins): remove types imports 2022-07-21 23:37:05 +02:00
Benjamin Canac
6b813bd3b3 chore: improve types and imports 2022-07-21 23:33:06 +02:00
Benjamin Canac
7b3263a621 chore(deps): add vue-tsc 2022-07-21 23:32:46 +02:00
Benjamin Canac
002a8f6803 docs: refactor dropdown avatar 2022-07-21 23:32:33 +02:00
Benjamin Canac
5a596eb2b0 chore(CommandPalette): command avatar is now an object 2022-07-21 23:31:59 +02:00
Benjamin Canac
2f87cf86a1 chore(Dropdown): item avatar is now an object 2022-07-21 23:31:20 +02:00
Benjamin Canac
e4f148efa9 fix(CommandPalette): icon color on hover 2022-07-21 19:19:29 +02:00
Benjamin Canac
024a5914b9 chore(CommandPalette): align chip and avatar size with icon 2022-07-21 18:49:43 +02:00
Sylvain Marroufin
ce28b04187 chore(CommandPalette): improve customization options (#71)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2022-07-21 15:46:05 +02:00
Benjamin Canac
1ff9fd4f69 chore(toast): improve notifications 2022-07-21 00:39:06 +02:00
Benjamin Canac
487f253e14 chore(SelectCustom): handle disabled on button 2022-07-20 10:40:43 +02:00
Benjamin Canac
f54d4d788d chore(SelectCustom): add disabled prop 2022-07-20 10:15:54 +02:00
Benjamin Canac
c04885dd79 chore(release): 0.0.3 2022-07-18 19:49:04 +02:00
Benjamin Canac
8d70dbdcfe chore(deps): bump nuxt 2022-07-18 19:29:33 +02:00
Benjamin Canac
597439b538 chore(CommandPalette): put back nullable prop 2022-07-18 19:17:14 +02:00
Benjamin Canac
dc1050225b docs: update 2022-07-18 17:32:12 +02:00
Benjamin Canac
b44e3af422 chore(CommandPalette): improve component 2022-07-18 17:31:54 +02:00
Benjamin Canac
d9f08b99ca chore(CommandPalette): remove click and to handling 2022-07-18 16:04:47 +02:00
Benjamin Canac
af870f90ec chore(CommandPalette): remove timeout on query clear 2022-07-18 15:37:35 +02:00
Benjamin Canac
222bb987a4 chore(CommandPalette): improve component 2022-07-18 15:28:39 +02:00
Benjamin Canac
ea293bae0c fix(CommandPalette): add missing import 2022-07-18 15:12:02 +02:00
Benjamin Canac
8a57ab60c7 chore(CommandPalette): support selected with modelValue 2022-07-18 15:08:10 +02:00
Benjamin Canac
9e0edc27ab chore(CommandPalette): improve component 2022-07-18 14:31:23 +02:00
Benjamin Canac
76ffbf4cf3 fix(CommandPalette): options priority 2022-07-17 17:39:44 +02:00
Benjamin Canac
4eb4b65167 chore(CommandPalette): one fuse per group 2022-07-17 17:21:15 +02:00
Benjamin Canac
cf65b4ab54 fix(CommandPaletteGroup): invalid spacing when no icon 2022-07-17 14:47:24 +02:00
Benjamin Canac
c4e2197584 chore(deps): remove @types/tailwindcss 2022-07-17 14:45:35 +02:00
Benjamin Canac
1b5f7f5e31 chore(CommandPalette): use defu to merge options 2022-07-17 14:24:32 +02:00
Benjamin Canac
1495ff987d fix(CommandPaletteGroup): fail replace on items 2022-07-17 14:18:49 +02:00
Benjamin Canac
503b9a6b5c fix(CommandPalette): slice from computed options 2022-07-17 14:03:46 +02:00
Benjamin Canac
5e4c49ae3a chore(CommandPalette): improve options and always slice results 2022-07-17 14:00:42 +02:00
Benjamin Canac
c42dc9ecd2 chore(CommandPalette): set default threshold to undefined 2022-07-17 13:44:23 +02:00
Benjamin Canac
18dceb7445 feat(CommandPalette): implement component 2022-07-17 12:37:16 +02:00
Benjamin Canac
1a8ca6fb38 docs: fix input with objects and arrays 2022-07-17 12:36:59 +02:00
Benjamin Canac
2d7f096a55 chore(deps): bump 2022-07-17 12:36:36 +02:00
Benjamin Canac
da4e8d5c09 fix(SelectCustom): add missing bg in list input 2022-07-17 00:29:22 +02:00
Benjamin Canac
5fdf5eede0 chore(Slideover): add flex-shrink-0 to header 2022-07-16 23:07:49 +02:00
Benjamin Canac
d413cf74d6 fix(SelectCustom): improve creatable placeholder 2022-07-16 18:55:08 +02:00
Benjamin Canac
5906ee9c64 chore(SelectCustom): add preset for empty option 2022-07-16 18:48:51 +02:00
Benjamin Canac
0e0f3e39d3 fix(SelectCustom): remove unused import 2022-07-16 18:40:57 +02:00
Benjamin Canac
7907b1274d chore(SelectCustom): improve component 2022-07-16 18:38:56 +02:00
Benjamin Canac
46ea467098 fix(plugins): error in provides 2022-07-15 22:11:45 +02:00
Benjamin Canac
1d8958c2fc chore(deps): fix build 2022-07-13 23:51:04 +02:00
Benjamin Canac
9a52db84b7 chore(deps): bump 2022-07-13 23:26:33 +02:00
Benjamin Canac
78dad77a44 chore(package): prepare docs 2022-07-13 17:04:54 +02:00
Benjamin Canac
144b3382ad chore: fix lint 2022-07-13 16:58:12 +02:00
Benjamin Canac
b00a3aa7d6 chore(plugins): use return provide 2022-07-13 16:45:48 +02:00
Benjamin Canac
498be15a29 chore(Icon): improve component 2022-07-13 16:45:39 +02:00
Benjamin Canac
95c018f63f chore(Notifications): use #imports instead of #app 2022-07-13 16:45:01 +02:00
Benjamin Canac
efe9ab7140 chore(tsconfig): link to docs dir 2022-07-13 16:44:35 +02:00
Benjamin Canac
2dd9771d0d chore(deps): bump @tailwindcss/typography 2022-07-13 16:44:21 +02:00
Benjamin Canac
4de20563c3 chore(deps): bump 2022-07-12 15:25:14 +02:00
Benjamin Canac
40fef2bdf5 chore(Slideover): improve header 2022-07-11 22:32:11 +02:00
Sylvain Marroufin
00b9a0839b feat(AvatarGroup): preset support (#69) 2022-07-11 16:21:56 +02:00
Benjamin Canac
0e1b9087af chore(deps): bump 2022-07-11 16:12:41 +02:00
Sylvain Marroufin
5b4e4f8648 feat(Slideover): preset support (#68)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2022-07-11 16:00:45 +02:00
Benjamin Canac
6e616408ea chore(SelectCustom): add default gap on option container 2022-07-04 19:34:05 +02:00
Benjamin Canac
b1bbbbb7d0 chore(SelectCustom): improve option slot 2022-07-03 14:31:50 +02:00
Benjamin Canac
db39a9cdba fix(SelectCustom): move max-height on base 2022-07-02 19:55:23 +02:00
Benjamin Canac
524f8411c5 fix(SelectCustom): missing padding on list 2022-07-02 19:00:44 +02:00
Benjamin Canac
22ee717105 chore(SelectCustom): support popper and improve slots 2022-07-02 17:21:45 +02:00
Benjamin Canac
eb6fbd9c4a fix(SelectCustom): add missing text-sm class 2022-07-02 00:38:29 +02:00
Benjamin Canac
ae6e8eec03 fix(SelectCustom): add missing listContainerClass prop 2022-07-02 00:35:53 +02:00
Benjamin Canac
2e64343610 fix(SelectCustom): icon name in prop only for now 2022-07-02 00:33:05 +02:00
Benjamin Canac
6da0c28019 fix(SelectCustom): prop is icon instead of iconName 2022-07-02 00:28:36 +02:00
Benjamin Canac
8f9e0a4a50 chore(SelectCustom): allow override of icon 2022-07-02 00:26:25 +02:00
Benjamin Canac
06426ad1e5 chore(Button): support trailing and leading at once 2022-07-01 22:04:01 +02:00
Benjamin Canac
7db0ca50a0 fix(toast): id should be a string 2022-06-30 17:47:41 +02:00
Benjamin Canac
10d89d3cd1 fix(module): search in tailwind colors for primary and gray 2022-06-30 17:36:50 +02:00
Benjamin Canac
00e0ab39f8 fix(utils): types 2022-06-30 17:21:08 +02:00
Benjamin Canac
5a8b078d65 feat(module): handle variants with dynamic colors 2022-06-30 17:16:22 +02:00
Benjamin Canac
f98253c255 chore(Modal): increase z-index 2022-06-30 13:52:57 +02:00
Benjamin Canac
7f7b158b7e chore(tailwind): add dark color-scheme 2022-06-30 12:30:49 +02:00
Benjamin Canac
bbbe57dedb fix(module): register typography plugin 2022-06-30 11:50:20 +02:00
Benjamin Canac
a689542de5 chore(deps): add @tailwindcss/typography 2022-06-30 11:48:59 +02:00
Benjamin Canac
847788b2a0 chore(Avatar): allow override of rounded class 2022-06-29 00:12:51 +02:00
Benjamin Canac
d733e25bf0 fix(VerticalNavigation): link.avatar is now an object 2022-06-29 00:02:10 +02:00
Benjamin Canac
f0835cf979 fix(VerticalNavigation): remove avatarSize prop 2022-06-28 23:59:57 +02:00
Benjamin Canac
33bc561759 chore(VerticalNavigation): bind avatar props from link 2022-06-28 23:49:58 +02:00
Benjamin Canac
8039351bee chore(VerticalNavigation): handle avatar 2022-06-28 22:34:39 +02:00
Benjamin Canac
7979bc6586 chore(presets): update input icon spacing for sm 2022-06-28 15:28:46 +02:00
Sylvain Marroufin
2f90ce2319 feat(Slideover): add close button in header (#65) 2022-06-24 15:40:26 +02:00
Sylvain Marroufin
aecfef20e6 feat(Slideover): allow opening from the right side (#64) 2022-06-23 15:58:58 +02:00
Benjamin Canac
c3a200d450 chore(Modal): align modals at bottom on mobile 2022-06-23 14:16:24 +02:00
Benjamin Canac
dd6463710c fix(Card): prevent empty sm: class when rounded-class is null 2022-06-23 14:15:52 +02:00
Benjamin Canac
33b1176bdd fix(input): background should go into appearance 2022-06-21 15:54:32 +02:00
renovate[bot]
ce899b4e27 chore(deps): update all non-major dependencies (#60)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-20 15:42:50 +02:00
Benjamin Canac
536bfe4ff1 chore(SelectCustom): improve icon placement and list text size 2022-06-20 10:46:32 +02:00
Sylvain Marroufin
6585bfc24a fix(Avatar): truncate placeholder if too long (#61) 2022-06-07 16:49:13 +02:00
renovate[bot]
b05aa48caa chore(deps): update all non-major dependencies (#58)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-25 16:15:28 +02:00
Sylvain Marroufin
e419d68f64 fix(Select): normalizedValue handling Object modelValue (#59) 2022-05-24 10:59:27 +02:00
Benjamin Canac
06b07e292e fix(Card): prevent double class 2022-05-23 12:17:42 +02:00
Benjamin Canac
33a31205a1 chore(components): add names 2022-05-20 12:21:50 +02:00
Benjamin Canac
4f7e286790 chore(deps): bump 2022-05-20 12:18:47 +02:00
Sébastien Chopin
c1a7629e29 feat(Icon): support custom component and emoji
Co-Authored-By: Benjamin Canac <canacb1@gmail.com>
Co-Authored-By: Sylvain Marroufin <marroufin.sylvain@gmail.com>
2022-05-19 12:08:13 +02:00
Benjamin Canac
189c9f3eda chore(Select): add appearance prop 2022-05-17 18:02:16 +02:00
Benjamin Canac
358387e358 fix(module): remove safelist on max-w 2022-05-17 17:15:34 +02:00
Benjamin Canac
3c724e89d4 chore(Button): add rounded preset 2022-05-17 17:15:24 +02:00
Benjamin Canac
4851a4b905 Revert "chore(package): remove postbuild:docs"
This reverts commit 4f789ac54b.
2022-05-11 17:23:23 +02:00
Benjamin Canac
4f789ac54b chore(package): remove postbuild:docs 2022-05-11 14:29:20 +02:00
Benjamin Canac
72756689b9 chore(deps): bump @nuxtjs/color-mode 2022-05-10 10:57:19 +02:00
Sylvain Marroufin
d980176e03 fix(Modal): widthClass prop and default preset value (#56) 2022-05-09 17:59:04 +02:00
Sylvain Marroufin
23deef325a feat(toast): add aliases for info and warning notifications 2022-05-09 16:12:57 +02:00
renovate[bot]
4b4d16503f chore(deps): update all non-major dependencies (#54)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-09 16:12:13 +02:00
Benjamin Canac
0154fa6e8c chore(deps): move back to nuxt3 edge channel 2022-05-09 16:10:50 +02:00
Sylvain Marroufin
39bf242f78 chore: migrate components to typescript setup (#55)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2022-05-09 16:05:07 +02:00
Benjamin Canac
6c2f93f262 chore(Notifications): reduce padding on lg 2022-05-08 17:08:04 +02:00
Benjamin Canac
5031610d05 chore(VerticalNavigation): override link iconClass 2022-05-07 01:01:04 +02:00
Benjamin Canac
b9757384a5 chore(VerticalNavigation): add link class 2022-05-07 00:56:42 +02:00
Benjamin Canac
60c72a20d1 fix(Notifications): inexistant z-55 2022-05-06 18:41:48 +02:00
Benjamin Canac
879bf36698 chore(deps): bump @nuxtjs/tailwindcss 2022-05-05 11:15:31 +02:00
Benjamin Canac
f65f4aa19e Revert "fix(package): update postbuild:docs command"
This reverts commit 8045c7b47e.
2022-05-04 17:49:17 +02:00
Benjamin Canac
9651f4a216 Revert "chore(package): remove postbuild:docs command"
This reverts commit 051d089928.
2022-05-04 17:49:15 +02:00
Benjamin Canac
dd292afa5e chore(deps): bump 2022-05-04 17:47:30 +02:00
Benjamin Canac
538f707757 docs: fix ts error 2022-05-04 17:46:28 +02:00
Benjamin Canac
051d089928 chore(package): remove postbuild:docs command 2022-05-03 17:18:58 +02:00
Benjamin Canac
8045c7b47e fix(package): update postbuild:docs command 2022-05-03 17:16:25 +02:00
Benjamin Canac
0e18526a6f fix(useTimer): lint rule changed 2022-05-02 15:26:32 +02:00
Benjamin Canac
cc561f971b chore(deps): bump @nuxtjs/eslint-config-typescript 2022-05-02 15:20:46 +02:00
Benjamin Canac
c4a40b065c fix(Card): nullable validator on card roundedClass prop 2022-04-29 15:21:26 +02:00
Benjamin Canac
8222d05c15 fix(SelectCustom): move wrapper on top of Listbox 2022-04-28 17:56:46 +02:00
Benjamin Canac
7d1c38c820 chore(Slideover): remove lg:hidden 2022-04-28 15:06:07 +02:00
Benjamin Canac
dfe86f0baf fix(Modal): move classes to DialogPanel 2022-04-28 15:05:51 +02:00
Benjamin Canac
6532b4bfdd chore(Slideover): migrate by using DialogPanel 2022-04-28 14:36:45 +02:00
Benjamin Canac
936d6a5fee fix(SelectCustom): move wrapper under Listbox 2022-04-28 12:59:43 +02:00
Benjamin Canac
cde6b5037d chore(Modal): migrate by using DialogPanel 2022-04-28 12:44:33 +02:00
Benjamin Canac
5de5c07203 chore(SelectCustom): add multiple prop 2022-04-28 12:44:18 +02:00
Benjamin Canac
0f25820a49 docs: fix margin on example 2022-04-28 12:44:08 +02:00
renovate[bot]
f48dec0d60 chore(deps): update all non-major dependencies (#51)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-28 12:29:04 +02:00
Sylvain Marroufin
f0bfe20572 fix(textarea): autoresize reactivity (#52) 2022-04-28 11:39:03 +02:00
Sylvain Marroufin
1bda2ec01f chore(typescript): migrate components (#53) 2022-04-28 11:38:23 +02:00
Benjamin Canac
57354f64f9 chore(deps): migrate to nuxt 3.0.0-rc.1 2022-04-21 00:20:03 +02:00
Benjamin Canac
035919a545 fix(Link): add missing inactive-class on button and a 2022-04-20 18:31:59 +02:00
Benjamin Canac
18c194e839 fix(Tooltip): prevent close when hovering 2022-04-19 16:09:04 +02:00
Benjamin Canac
15b4f913ae chore(deps): bump 2022-04-19 16:08:53 +02:00
Benjamin Canac
723f075c56 fix(AvatarGroup): pass all avatar props 2022-04-15 11:59:40 +02:00
Benjamin Canac
39790058af chore(Popover): remove default panel-class prop 2022-04-12 16:01:59 +02:00
Benjamin Canac
2e099392f1 fix: Dropdown and Popover manual padding 2022-04-12 15:04:56 +02:00
Sylvain Marroufin
8bc4902078 fix: Hover mode on Dropdown & Popover (#48) 2022-04-12 11:44:35 +02:00
Benjamin Canac
656b6e1c59 fix(Popover): add missing onMounted import 2022-04-11 15:41:41 +02:00
Benjamin Canac
2eb2feab67 fix(Popover): add missing ref import 2022-04-11 15:39:24 +02:00
renovate[bot]
fc4de89dad chore(deps): update devdependency eslint to ^8.13.0 (#46)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-11 15:26:43 +02:00
Sylvain Marroufin
c431f8b4a1 feat(Popover): handle hovering mode (#47) 2022-04-11 15:26:33 +02:00
Benjamin Canac
289de07ba1 docs: add Slideover component 2022-04-08 10:56:16 +02:00
Benjamin Canac
368f93c294 chore(overlays): darken opacity overlay for modal and slideover 2022-04-08 10:56:16 +02:00
renovate[bot]
49afd2d404 chore(deps): update all non-major dependencies (#44)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-08 10:23:43 +02:00
Sylvain Marroufin
77149f0dd4 feat(Dropdown): add hover mode (#45) 2022-04-08 10:23:29 +02:00
Benjamin Canac
3ff8ae70b1 chore(Slideover): increase header padding on sm 2022-04-07 11:31:52 +02:00
Benjamin Canac
0d51747f4a chore(Popover): add close slot prop 2022-04-04 17:15:05 +02:00
renovate[bot]
5c45ac9882 chore(deps): update all non-major dependencies (#40)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-04 14:19:53 +02:00
Sylvain Marroufin
ba643d9faa fix(Textarea): autoresize (#43) 2022-04-04 14:19:23 +02:00
Benjamin Canac
571e907acd chore(Slideover): fix types 2022-03-31 10:56:36 +02:00
Benjamin Canac
065d3691ac chore(Slideover): move height to header child 2022-03-30 18:42:15 +02:00
Benjamin Canac
564ae86d63 chore(Slideover): improve header slot 2022-03-30 18:41:28 +02:00
Benjamin Canac
cf021a5888 fix(Slideover): remove useless padding 2022-03-30 18:29:44 +02:00
Benjamin Canac
7237dcb10f chore(Slideover): add component 2022-03-30 17:45:53 +02:00
Benjamin Canac
0ca40a57c9 chore(Dropdown): handle item hover
Co-Authored-By: Sébastien Chopin <seb@nuxtjs.com>
2022-03-26 00:45:16 +01:00
Benjamin Canac
3c7d986684 chore(deps): bump 2022-03-25 17:36:11 +01:00
Benjamin Canac
c59b45cf3c chore(VerticalNavigation): add relative class to base 2022-03-23 14:32:06 +01:00
renovate[bot]
685f738d5c chore(deps): update all non-major dependencies (#28)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-17 16:30:12 +01:00
Sylvain Marroufin
eb0043914b fix(Select): default value handling 2022-03-17 15:39:09 +01:00
Benjamin Canac
cc01af8e55 fix(Avatar): add missing watch import 2022-03-14 17:05:58 +01:00
Benjamin Canac
eb41b23432 fix(Avatar): missing ref import 2022-03-14 17:01:44 +01:00
Benjamin Canac
f38b920305 chore(Modal): add preset system 2022-03-14 16:55:19 +01:00
Benjamin Canac
dca0ff4a60 docs: improve slots component display 2022-03-14 16:55:19 +01:00
Sylvain Marroufin
fb3ff2e5fa fix(Avatar): url error handling (#39) 2022-03-14 16:51:16 +01:00
Benjamin Canac
9554e801c2 fix(popper): use $el after 1.5 upgrade
https://github.com/tailwindlabs/headlessui/discussions/1125#discussioncomment-2299441
2022-03-14 12:46:44 +01:00
Benjamin Canac
cdb4b1f233 chore(deps): bump 2022-03-14 12:46:15 +01:00
Benjamin Canac
3305f28c22 docs: remove useless example 2022-03-14 12:46:08 +01:00
Benjamin Canac
e8a1b46598 chore(AlertDialog): supper appear and close 2022-03-11 16:22:03 +01:00
Benjamin Canac
c71fc69b4b chore(Modal): add appear to transition child 2022-03-11 15:28:13 +01:00
Benjamin Canac
fc2d17cbc3 chore(Modal): add appear prop 2022-03-11 15:17:51 +01:00
Benjamin Canac
850c766fab fix(Modal): prevent attrs inherit 2022-03-10 16:33:40 +01:00
Benjamin Canac
da3ed26c2c fix(Avatar): prevent boolean src 2022-03-01 17:22:26 +01:00
Benjamin Canac
4d0709a25c docs: remove useless onSubmit 2022-03-01 17:03:16 +01:00
Benjamin Canac
7723704f79 fix(Select): disable placeholder 2022-03-01 17:01:48 +01:00
Benjamin Canac
77372423d8 fix(Link): use exact 2022-03-01 12:19:47 +01:00
Benjamin Canac
d4b65996ce fix(Link): send correct active 2022-03-01 12:15:23 +01:00
Benjamin Canac
55565d7079 chore(Avatar): use preset system 2022-02-25 17:57:55 +01:00
Benjamin Canac
858b7da3c0 chore(Avatar): add slot 2022-02-25 16:49:23 +01:00
Benjamin Canac
75440608d0 chore: remove unocss-include comments 2022-02-25 16:49:15 +01:00
Benjamin Canac
1e6ad72644 fix(SelectCustom): dark mode preset 2022-02-24 12:53:06 +01:00
Benjamin Canac
6cfdebb564 chore(module): transpile @iconify/vue 2022-02-24 12:52:51 +01:00
Benjamin Canac
7e85c07d72 chore(presets): remove nuxt 2022-02-23 16:25:26 +01:00
Benjamin Canac
a584d62119 chore(Link): remove inactiveClass when button or external 2022-02-23 16:21:12 +01:00
Benjamin Canac
ceedbe0bbd fix(Link): exact handling 2022-02-23 15:52:29 +01:00
Benjamin Canac
aef11562c9 fix(Link): handle isActive through vue-router 2022-02-23 15:49:07 +01:00
Benjamin Canac
09aed4b752 fix(SelectCustom): add tabindex -1 to hidden input 2022-02-23 12:47:44 +01:00
Benjamin Canac
0d98d44045 chore(Pills): refactor component 2022-02-23 12:38:58 +01:00
Sylvain Marroufin
c3facb1fef fix(Icon): missing imports 2022-02-22 12:00:21 +01:00
Sylvain Marroufin
78021d3850 fix(Icon): reload icon when prop name changes 2022-02-22 11:49:55 +01:00
Benjamin Canac
3d9ead3d8d chore(Tabs): use preset system 2022-02-22 11:42:06 +01:00
Benjamin Canac
5fb7f10283 fix(Dropdown): improve disabled state 2022-02-22 11:29:49 +01:00
Benjamin Canac
95b4b10c8e docs: fix dark mode blink 2022-02-22 11:22:15 +01:00
Benjamin Canac
b3e288aabc chore(deps): bump @nuxtjs/color-mode 2022-02-21 16:47:49 +01:00
Benjamin Canac
ba15aed671 docs: update 2022-02-18 16:35:47 +01:00
Benjamin Canac
7980e7f691 chore(deps): lock headlessui version to 1.4.3 for now 2022-02-18 16:35:41 +01:00
Baptiste Leproux
46ca8c5422 feat(button): Add black variant (#34)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2022-02-18 11:59:40 +01:00
Benjamin Canac
fc50834448 docs: update 2022-02-18 11:58:52 +01:00
Benjamin Canac
f1a830f7a6 chore(module): use @nuxtjs/color-mode 2022-02-18 11:58:38 +01:00
Benjamin Canac
3ab0698d7f fix(colors): move primary to safeColors 2022-02-18 11:31:43 +01:00
Benjamin Canac
6fc9466e8e docs: add github repo button 2022-02-18 11:31:30 +01:00
Benjamin Canac
6484575490 docs: update 2022-02-18 11:23:43 +01:00
Benjamin Canac
9406124ee1 chore(deps): bump 2022-02-18 11:23:43 +01:00
Sylvain Marroufin
4532e09ac0 feat(clipboard): replace navigator.clipboard with vueuse useClipboard (#33) 2022-02-17 17:48:13 +01:00
Benjamin Canac
e1d79d7fe7 fix(presets): add disabled bg color on nuxt buttons 2022-02-16 15:50:30 +01:00
Benjamin Canac
516e7faf8f fix(presets): dropdown avatar position 2022-02-16 12:30:42 +01:00
Benjamin Canac
c0661c3580 chore(Dropdown): support item avatar 2022-02-16 12:15:26 +01:00
Benjamin Canac
223a477a5e chore(SelectCustom): add slot on button 2022-02-15 12:54:22 +01:00
Benjamin Canac
8492e161db fix(SelectCustom): add default value to placeholder 2022-02-15 12:47:46 +01:00
Benjamin Canac
4e0d23ed34 fix(SelectCustom): handle placeholder when value is null 2022-02-15 12:45:37 +01:00
Benjamin Canac
13193969ee chore(presets): add shadow to card 2022-02-14 18:39:57 +01:00
Benjamin Canac
a01286f427 chore(deps): fix lock 2022-02-14 17:19:37 +01:00
Benjamin Canac
f67fdb7d2f fix(colors): hard-code colors as tailwindcss/colors is different 2022-02-14 16:59:55 +01:00
Benjamin Canac
541bcf6d76 chore(deps): bump and force tailwindcss version 2022-02-14 16:55:09 +01:00
Benjamin Canac
90ff1c0671 fix(Toggle): add v-if when icon props not defined 2022-02-14 16:54:57 +01:00
Benjamin Canac
4c891225b1 fix(module): use variants key for safelist 2022-02-14 16:34:33 +01:00
Benjamin Canac
c636ff24ba chore(module): improve safelist regex 2022-02-14 15:50:03 +01:00
Benjamin Canac
93c9fe1c74 fix(module): move colors utils to runtime dir 2022-02-14 15:26:34 +01:00
Benjamin Canac
4a648850f5 chore(module): safelist dynamic colors 2022-02-14 14:59:52 +01:00
Benjamin Canac
382e3b8e52 chore(Icon): improve component 2022-02-11 18:13:25 +01:00
Benjamin Canac
f513ea6ca8 fix(Icon): name can be an object 2022-02-11 17:59:23 +01:00
Benjamin Canac
b2705feedc fix(module): parse presets with mjs ext 2022-02-11 17:46:37 +01:00
Benjamin Canac
b8dd80d3d3 fix(module): presets content 2022-02-11 17:43:31 +01:00
Benjamin Canac
65aa1690ba fix(module): resolve runtime dir 2022-02-11 17:27:09 +01:00
Benjamin Canac
619f620b7e fix(SelectCustom): add missing required prop 2022-02-11 17:22:06 +01:00
Benjamin Canac
5b050ee52d chore(deps): move 2022-02-11 17:17:54 +01:00
Benjamin Canac
702abf7a9f feat: migrate to @nuxtjs/tailwindcss (#32) 2022-02-11 17:13:09 +01:00
Florent Delerue
1bec8d163c fix(Avatar): placeholder (#31) 2022-02-11 15:23:16 +01:00
Benjamin Canac
a9f1d937bc fix(presets): support dark ring-offset-color 2022-02-10 12:00:34 +01:00
Benjamin Canac
185273f3e9 fix(module): import colors from preset-mini 2022-02-10 12:00:23 +01:00
Benjamin Canac
757c38e75c chore(deps): bump 2022-02-10 12:00:08 +01:00
Sylvain Marroufin
6bd51975bf feat(toast): expose timeout to alias methods (#30)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2022-02-09 17:13:59 +01:00
Benjamin Canac
b7f6bf93d3 chore(presets): add card shadow 2022-02-09 17:11:48 +01:00
Benjamin Canac
b5edfaddd4 chore(presets): override container for nuxt preset 2022-02-08 18:41:30 +01:00
Benjamin Canac
45cf898ec3 fix(preset): replace avatar wrapper with inline-flex 2022-02-08 15:44:09 +01:00
Benjamin Canac
57d477f3a2 chore(Dropdown): add inline-flex on trigger 2022-02-07 14:48:39 +01:00
Benjamin Canac
d745d1aa9a chore(deps): bump 2022-02-07 14:48:27 +01:00
Benjamin Canac
5288dce744 docs: add favicon 2022-02-07 14:48:19 +01:00
Benjamin Canac
760da3d1a8 fix(Button): wrong config for icon size class 2022-02-04 17:02:34 +01:00
Sylvain Marroufin
832ffe4323 feat(plugins): clipboard (#29) 2022-02-02 17:14:41 +01:00
Benjamin Canac
ed499b3b21 fix(Avatar): remove gradient support 2022-02-02 14:32:57 +01:00
Benjamin Canac
5e44c54ae5 chore(module): exclude gradient-avatar from vite optimizeDeps 2022-02-02 12:16:44 +01:00
Benjamin Canac
d724f87654 chore(Dropdown): support preset 2022-02-02 12:15:01 +01:00
Benjamin Canac
af566ab1fa fix(Notifications): default value in useState 2022-02-02 12:06:27 +01:00
Benjamin Canac
85c8210ede fix(module): gradient-avatar include 2022-02-02 12:06:14 +01:00
Benjamin Canac
07df347fe3 chore(deps): bump 2022-02-02 11:56:40 +01:00
Benjamin Canac
e0342184fe fix(presets): defu merging 2022-02-01 14:52:37 +01:00
Benjamin Canac
e52d22de34 chore(VerticalNavigation): move spacing to specific prop 2022-02-01 14:28:56 +01:00
Benjamin Canac
a564f79b0c chore(module): add nuxt preset 2022-02-01 12:35:03 +01:00
152 changed files with 13647 additions and 7898 deletions

View File

@@ -15,10 +15,10 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [16]
node: [18]
steps:
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
@@ -29,7 +29,7 @@ jobs:
fetch-depth: 0
- name: Cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
@@ -41,6 +41,9 @@ jobs:
- name: Lint
run: yarn lint
- name: Typecheck
run: yarn typecheck
- name: Build
run: yarn build

View File

@@ -15,10 +15,10 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [16]
node: [18]
steps:
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
@@ -29,7 +29,7 @@ jobs:
fetch-depth: 0
- name: Cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
@@ -41,6 +41,9 @@ jobs:
- name: Lint
run: yarn lint
- name: Typecheck
run: yarn typecheck
- name: Build
run: yarn build

View File

@@ -2,6 +2,418 @@
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.0.0](https://github.com/nuxtlabs/ui/compare/v1.2.11...v2.0.0) (2023-05-04)
### Features
* rewrite to use app config and rework docs ([#143](https://github.com/nuxtlabs/ui/issues/143)) ([6da0db0](https://github.com/nuxtlabs/ui/commit/6da0db0113733df1a03220cb528bea862b553f37))
### [1.2.11](https://github.com/nuxtlabs/ui/compare/v1.2.10...v1.2.11) (2023-05-04)
### Bug Fixes
* **defineShortcuts:** use `useEventListener` ([#150](https://github.com/nuxtlabs/ui/issues/150)) ([59f62d3](https://github.com/nuxtlabs/ui/commit/59f62d322f07919d16a8d35340c3aa038cd09520))
### [1.2.10](https://github.com/nuxtlabs/ui/compare/v1.2.9...v1.2.10) (2023-04-07)
### Bug Fixes
* **CommandPalette:** typecheck ([cfce152](https://github.com/nuxtlabs/ui/commit/cfce1524b209212d9ce635b61376ff0d6bc3601b))
### [1.2.9](https://github.com/nuxtlabs/ui/compare/v1.2.8...v1.2.9) (2023-04-07)
### [1.2.8](https://github.com/nuxtlabs/ui/compare/v1.2.7...v1.2.8) (2023-04-04)
### [1.2.7](https://github.com/nuxtlabs/ui/compare/v1.2.6...v1.2.7) (2023-04-04)
### Bug Fixes
* **useTimer:** remaining after pause ([aafdfdb](https://github.com/nuxtlabs/ui/commit/aafdfdb59c365c542f93703dd52b4306ac935040))
### [1.2.6](https://github.com/nuxtlabs/ui/compare/v1.2.5...v1.2.6) (2023-04-04)
### [1.2.5](https://github.com/nuxtlabs/ui/compare/v1.2.4...v1.2.5) (2023-04-04)
### [1.2.4](https://github.com/nuxtlabs/ui/compare/v1.2.3...v1.2.4) (2023-04-04)
### [1.2.3](https://github.com/nuxtlabs/ui/compare/v1.2.2...v1.2.3) (2023-03-22)
### [1.2.2](https://github.com/nuxtlabs/ui/compare/v1.2.1...v1.2.2) (2023-03-20)
### [1.2.1](https://github.com/nuxtlabs/ui/compare/v1.2.0...v1.2.1) (2023-03-20)
### Bug Fixes
* **defineShortcuts:** shift + alphabetic character handling ([#140](https://github.com/nuxtlabs/ui/issues/140)) ([377b418](https://github.com/nuxtlabs/ui/commit/377b4189ca85603db0b7f040949260ba7494c46f))
## [1.2.0](https://github.com/nuxtlabs/ui/compare/v1.1.4...v1.2.0) (2023-03-09)
### Bug Fixes
* **defineShortcuts:** add missing import ([37b2271](https://github.com/nuxtlabs/ui/commit/37b2271bf04adfe6bee4d984fa12452b2168318c))
* **Tooltip:** `shortcutsClass` prop type ([fa49d52](https://github.com/nuxtlabs/ui/commit/fa49d52f17752eaa06f997a9b6e8df8adcab983f))
### [1.1.4](https://github.com/nuxtlabs/ui/compare/v1.1.3...v1.1.4) (2023-03-02)
### [1.1.3](https://github.com/nuxtlabs/ui/compare/v1.1.2...v1.1.3) (2023-03-02)
### [1.1.2](https://github.com/nuxtlabs/ui/compare/v1.1.1...v1.1.2) (2023-02-28)
### Bug Fixes
* **Tooltip:** truncate ([d08e64d](https://github.com/nuxtlabs/ui/commit/d08e64d53fa439f34d51909bcb6812f1bcd95d83))
* **VerticalNavigation:** links `to` type ([7970aef](https://github.com/nuxtlabs/ui/commit/7970aefcb032ce01fcb11e9285fa61ce87f59519))
### [1.1.1](https://github.com/nuxtlabs/ui/compare/v1.1.0...v1.1.1) (2023-02-20)
## [1.1.0](https://github.com/nuxtlabs/ui/compare/v1.0.0...v1.1.0) (2023-02-17)
### Features
* **CommandPalette:** handle async search for specific groups ([efa9674](https://github.com/nuxtlabs/ui/commit/efa9674815ab4de756079690da0a381c3703d564))
### Bug Fixes
* **CommandPalette:** types ([4702a4f](https://github.com/nuxtlabs/ui/commit/4702a4f10379201c167cc52099519778756a5780))
## [1.0.0](https://github.com/nuxtlabs/ui/compare/v0.2.1...v1.0.0) (2023-02-17)
### Features
* migrate to `@egoist/tailwindcss-icons` ([ee33522](https://github.com/nuxtlabs/ui/commit/ee3352278cf03fdd12f2a4663b403052de3f089a))
### [0.2.1](https://github.com/nuxtlabs/ui/compare/v0.2.0...v0.2.1) (2023-02-16)
## [0.2.0](https://github.com/nuxtlabs/ui/compare/v0.1.39...v0.2.0) (2023-02-16)
### [0.1.39](https://github.com/nuxtlabs/ui/compare/v0.1.38...v0.1.39) (2023-02-16)
### Features
* use `nuxt-icon` ([f5d068b](https://github.com/nuxtlabs/ui/commit/f5d068be9d5778b3d4fcdc11d06d9d765e62075d))
### Bug Fixes
* **SelectCustom:** handle search on string arrays ([6018f00](https://github.com/nuxtlabs/ui/commit/6018f009a86cca196d15e4e72dd5eb41aaeb4bad))
### [0.1.38](https://github.com/nuxtlabs/ui/compare/v0.1.37...v0.1.38) (2023-02-03)
### [0.1.37](https://github.com/nuxtlabs/ui/compare/v0.1.36...v0.1.37) (2023-02-03)
### Bug Fixes
* **CommandPalette:** improve accessibility ([#129](https://github.com/nuxtlabs/ui/issues/129)) ([bea47b5](https://github.com/nuxtlabs/ui/commit/bea47b5906d1bc665717830d6dc2f3ff2a0374f3))
### [0.1.36](https://github.com/nuxtlabs/ui/compare/v0.1.35...v0.1.36) (2023-02-02)
### Bug Fixes
* **CommandPalette:** put back cursor on top only when query changes ([5bf5a31](https://github.com/nuxtlabs/ui/commit/5bf5a314c414b96c656190719bd56acca10676f5))
### [0.1.35](https://github.com/nuxtlabs/ui/compare/v0.1.34...v0.1.35) (2023-02-01)
### Bug Fixes
* **AvatarGroup:** preset size prop ([c90cd9c](https://github.com/nuxtlabs/ui/commit/c90cd9c4f37bc3ce5f6e13f3279dc2c574c76524))
* **Dropdown:** lint ([1c4d46e](https://github.com/nuxtlabs/ui/commit/1c4d46e056adf84d69462a12af8ac29f93cbf87a))
* **Dropdown:** prevent panel display when no items ([a764486](https://github.com/nuxtlabs/ui/commit/a7644860b8c22a0163e01ca2c0eab2c48b09745a))
### [0.1.34](https://github.com/nuxtlabs/ui/compare/v0.1.33...v0.1.34) (2023-01-27)
### Bug Fixes
* **CommandPalette:** typecheck ([27717a5](https://github.com/nuxtlabs/ui/commit/27717a55b3e5120f32fba2bcea30f5a91262f1c5))
### [0.1.33](https://github.com/nuxtlabs/ui/compare/v0.1.32...v0.1.33) (2023-01-27)
### [0.1.32](https://github.com/nuxtlabs/ui/compare/v0.1.31...v0.1.32) (2023-01-23)
### [0.1.31](https://github.com/nuxtlabs/ui/compare/v0.1.30...v0.1.31) (2023-01-17)
### [0.1.30](https://github.com/nuxtlabs/ui/compare/v0.1.29...v0.1.30) (2023-01-17)
### [0.1.29](https://github.com/nuxtlabs/ui/compare/v0.1.28...v0.1.29) (2023-01-17)
### [0.1.28](https://github.com/nuxtlabs/ui/compare/v0.1.27...v0.1.28) (2023-01-13)
### [0.1.27](https://github.com/nuxtlabs/ui/compare/v0.1.26...v0.1.27) (2023-01-12)
### [0.1.26](https://github.com/nuxtlabs/ui/compare/v0.1.25...v0.1.26) (2023-01-09)
### Bug Fixes
* **CommandPalette:** select first item on search changes ([#126](https://github.com/nuxtlabs/ui/issues/126)) ([4f56921](https://github.com/nuxtlabs/ui/commit/4f56921096f5885cdab8b7cb5c5aa01304188e11))
### [0.1.25](https://github.com/nuxtlabs/ui/compare/v0.1.24...v0.1.25) (2023-01-09)
### [0.1.24](https://github.com/nuxtlabs/ui/compare/v0.1.23...v0.1.24) (2023-01-04)
### [0.1.23](https://github.com/nuxtlabs/ui/compare/v0.1.22...v0.1.23) (2022-12-20)
### [0.1.22](https://github.com/nuxtlabs/ui/compare/v0.1.21...v0.1.22) (2022-12-19)
### [0.1.21](https://github.com/nuxtlabs/ui/compare/v0.1.20...v0.1.21) (2022-12-19)
### [0.1.20](https://github.com/nuxtlabs/ui/compare/v0.1.19...v0.1.20) (2022-12-19)
### Bug Fixes
* avoid referring to complex types in props ([#123](https://github.com/nuxtlabs/ui/issues/123)) ([ff9f6c2](https://github.com/nuxtlabs/ui/commit/ff9f6c251df59641862d82587e5d963c8e6ea298))
### [0.1.19](https://github.com/nuxtlabs/ui/compare/v0.1.18...v0.1.19) (2022-12-16)
### [0.1.18](https://github.com/nuxtlabs/ui/compare/v0.1.17...v0.1.18) (2022-12-15)
### [0.1.17](https://github.com/nuxtlabs/ui/compare/v0.1.16...v0.1.17) (2022-12-06)
### Bug Fixes
* remove stop propagation on mode hover ([16fd1c0](https://github.com/nuxtlabs/ui/commit/16fd1c0ca38f1438e791c0d44399f590d9f20d02))
### [0.1.16](https://github.com/nuxtlabs/ui/compare/v0.1.15...v0.1.16) (2022-12-06)
### Bug Fixes
* **Popover:** preset from tooltip ([0ade69d](https://github.com/nuxtlabs/ui/commit/0ade69de2689b094b11a2dead8f71e3d2dccd552))
### [0.1.15](https://github.com/nuxtlabs/ui/compare/v0.1.14...v0.1.15) (2022-12-02)
### Bug Fixes
* **Dropdown:** better handle item click to preventDefault ([#119](https://github.com/nuxtlabs/ui/issues/119)) ([44a78d7](https://github.com/nuxtlabs/ui/commit/44a78d7f679812c59d4410d0bbc01f09abaa78dd))
### [0.1.14](https://github.com/nuxtlabs/ui/compare/v0.1.13...v0.1.14) (2022-12-02)
### [0.1.13](https://github.com/nuxtlabs/ui/compare/v0.1.12...v0.1.13) (2022-12-01)
### [0.1.12](https://github.com/nuxtlabs/ui/compare/v0.1.11...v0.1.12) (2022-11-29)
### Bug Fixes
* **Checkbox:** types ([629bb72](https://github.com/nuxtlabs/ui/commit/629bb724249cfe1fdd999cf52f8e72ca444bd94d))
### [0.1.11](https://github.com/nuxtlabs/ui/compare/v0.1.10...v0.1.11) (2022-11-29)
### Bug Fixes
* **Checkbox:** revert type fix as it breaks checkboxes ([7f18c6b](https://github.com/nuxtlabs/ui/commit/7f18c6bdc7c0054b2e5d4f9bf4e362847a3ba3a3))
### [0.1.10](https://github.com/nuxtlabs/ui/compare/v0.1.9...v0.1.10) (2022-11-26)
### Bug Fixes
* default popper options ([1ad9606](https://github.com/nuxtlabs/ui/commit/1ad96065fd706d828b906db3a5d578226ff08c36))
* default props for command palette ([#116](https://github.com/nuxtlabs/ui/issues/116)) ([952786e](https://github.com/nuxtlabs/ui/commit/952786ed79cd9cdf523f6eac5958f68790bacbea))
### [0.1.9](https://github.com/nuxtlabs/ui/compare/v0.1.8...v0.1.9) (2022-11-25)
### Bug Fixes
* **Icon:** couldn't load anymore ([6321d3e](https://github.com/nuxtlabs/ui/commit/6321d3ed8d5c9478cb1dafc1da94b21d0c7edb88))
* **Icon:** eslint ignore ([bc0c168](https://github.com/nuxtlabs/ui/commit/bc0c168c0b0feae96d6a1848c3a356d846e2cbb5))
### [0.1.8](https://github.com/nuxtlabs/ui/compare/v0.1.7...v0.1.8) (2022-11-16)
### [0.1.7](https://github.com/nuxtlabs/ui/compare/v0.1.6...v0.1.7) (2022-11-16)
### [0.1.6](https://github.com/nuxtlabs/ui/compare/v0.1.5...v0.1.6) (2022-11-15)
### Bug Fixes
* **SelectCustom:** add `w-full` on `ComboboxButton` ([3a300f7](https://github.com/nuxtlabs/ui/commit/3a300f72c1eca756ffd8f07ab871bf9c7bd7868d))
### [0.1.5](https://github.com/nuxtlabs/ui/compare/v0.1.4...v0.1.5) (2022-11-08)
### [0.1.4](https://github.com/nuxtlabs/ui/compare/v0.1.3...v0.1.4) (2022-11-08)
### Bug Fixes
* **button:** support `RouteLocationRaw` type for `to` ([#112](https://github.com/nuxtlabs/ui/issues/112)) ([1b56b50](https://github.com/nuxtlabs/ui/commit/1b56b50d4d54a5cb9e5febacdf42867988ae6c5d))
### [0.1.3](https://github.com/nuxtlabs/ui/compare/v0.1.2...v0.1.3) (2022-11-04)
### Bug Fixes
* **docs:** component input string field ([e521e1a](https://github.com/nuxtlabs/ui/commit/e521e1ac2421dc331652f1ea4ac2b0b2959dc069))
* **types:** add missing field in command palette type ([#111](https://github.com/nuxtlabs/ui/issues/111)) ([28586c3](https://github.com/nuxtlabs/ui/commit/28586c35a558d9e925094f86e07acdb928d54ad7))
### [0.1.2](https://github.com/nuxtlabs/ui/compare/v0.1.1...v0.1.2) (2022-10-27)
### [0.1.1](https://github.com/nuxtlabs/ui/compare/v0.1.0...v0.1.1) (2022-10-26)
### Bug Fixes
* **CommandPalette:** lint ([6fab68b](https://github.com/nuxtlabs/ui/commit/6fab68baa836c97680446e8cfdee7c5a64008539))
* **Dropdown:** close on click item with `to` ([#103](https://github.com/nuxtlabs/ui/issues/103)) ([5517cc2](https://github.com/nuxtlabs/ui/commit/5517cc28467f957956a20126c1ce48e64677cb82))
* **Popover:** avoid crash on mount if ref not loaded ([#105](https://github.com/nuxtlabs/ui/issues/105)) ([e9f0224](https://github.com/nuxtlabs/ui/commit/e9f0224b919445260d3b19511420a3fd448e4ea7))
* **SelectCustom:** types and lint ([ec8bd5c](https://github.com/nuxtlabs/ui/commit/ec8bd5cdc49feb924dfdff352d9f3d788653c583))
## [0.1.0](https://github.com/nuxtlabs/ui/compare/v0.0.3...v0.1.0) (2022-10-25)
### Bug Fixes
* `to` prop type ([be94fea](https://github.com/nuxtlabs/ui/commit/be94fea84acc69c0114099b5251ff34e3a239726))
* **CommandPalette:** command icons opacity in dark mode ([abb93b5](https://github.com/nuxtlabs/ui/commit/abb93b5fd3ddda8c91db3370c8e3109cff4a091d))
* **CommandPalette:** fix groups computed ([9302b8d](https://github.com/nuxtlabs/ui/commit/9302b8d635c3ffb489142601a17a9878072c89fe))
* **CommandPalette:** group items spacing ([32922de](https://github.com/nuxtlabs/ui/commit/32922def7deec5bee920a1fb1400449461315d0d))
* **CommandPalette:** hack for reactivity ([b43394d](https://github.com/nuxtlabs/ui/commit/b43394ddc3ee795b56679f7076e0c80a1c496b2e))
* **CommandPalette:** icon color on hover ([e4f148e](https://github.com/nuxtlabs/ui/commit/e4f148efa97adf52b1b5544ff6c349a4ac82a956))
* **CommandPalette:** icon inactive opacity on dark mode ([5722a3a](https://github.com/nuxtlabs/ui/commit/5722a3ae62706229179b75d9291babd1c2039244))
* **CommandPalette:** prevent empty active slot ([056ab30](https://github.com/nuxtlabs/ui/commit/056ab304745c3ba8dedbf9d6491839b9d620df88))
* **CommandPalette:** prevent shortcuts to disappear on hover ([f87252f](https://github.com/nuxtlabs/ui/commit/f87252f05debda7c98f5ab8a9453e57efafaad0f))
* **CommandPalette:** reactivity issue + improve results ([ec9f670](https://github.com/nuxtlabs/ui/commit/ec9f67094a51e3afde92f7924b8ee5d4e9053158)), closes [#95](https://github.com/nuxtlabs/ui/issues/95) [#96](https://github.com/nuxtlabs/ui/issues/96)
* **CommandPalette:** truncate suffix ([aa242aa](https://github.com/nuxtlabs/ui/commit/aa242aa87d5ae834d838518efd530003fdde5e24))
* default object options ([95c14a4](https://github.com/nuxtlabs/ui/commit/95c14a43600016bf405b557752fad289fb31154a))
* **Dropdown:** increase timeout for hover mode ([7291942](https://github.com/nuxtlabs/ui/commit/72919425b6e84581ba3b854aec3348977b335a3f))
* error in Popover and Dropdown ([541ed30](https://github.com/nuxtlabs/ui/commit/541ed304a0a4fa2646115547e03e44cf9e17c65e))
* **icon:** hydratation warning when loading same icon twice ([#99](https://github.com/nuxtlabs/ui/issues/99)) ([d57647a](https://github.com/nuxtlabs/ui/commit/d57647a77a145ff6e81d3a71550e98e3eaf3a842))
* load icons on mount rather than within setup ([#82](https://github.com/nuxtlabs/ui/issues/82)) ([62361bf](https://github.com/nuxtlabs/ui/commit/62361bfa8f77c2f3452af108f08434ba4c6ec4c5))
* **Modal:** use object for `innerStyle` ([72dc0d0](https://github.com/nuxtlabs/ui/commit/72dc0d0d0c270b2dfbf2ba8a8eb03a04eb5dea9a))
* **Notification:** improve placement with description ([945fec6](https://github.com/nuxtlabs/ui/commit/945fec62c2efa6baf7b32c8a85ba658dfd9311c9))
* **Notification:** prevent error without timeout ([8a66f5e](https://github.com/nuxtlabs/ui/commit/8a66f5e9bf65ab04b8878f0d597e439b45b46bb3))
* **Popover:** `inline-flex` on trigger button ([593573a](https://github.com/nuxtlabs/ui/commit/593573a286459b48fde8f49df2c2f1fc1dc98da6))
* **SelectCustom:** avoid submitting to form when closing ([#83](https://github.com/nuxtlabs/ui/issues/83)) ([cf1b2cd](https://github.com/nuxtlabs/ui/commit/cf1b2cdd133233481da6e1ec47b49b7f012aa204))
### [0.0.3](https://github.com/nuxtlabs/ui/compare/v0.0.2...v0.0.3) (2022-07-18)
### Features
* **AvatarGroup:** preset support ([#69](https://github.com/nuxtlabs/ui/issues/69)) ([00b9a08](https://github.com/nuxtlabs/ui/commit/00b9a0839b3fb19521080684cf3f4e46cf4c64e5))
* **button:** Add black variant ([#34](https://github.com/nuxtlabs/ui/issues/34)) ([46ca8c5](https://github.com/nuxtlabs/ui/commit/46ca8c5422cc00a2c66376d910b9669ab82002d0))
* **clipboard:** replace `navigator.clipboard` with vueuse `useClipboard` ([#33](https://github.com/nuxtlabs/ui/issues/33)) ([4532e09](https://github.com/nuxtlabs/ui/commit/4532e09ac067518fe607d7079b2761c783f36505))
* **CommandPalette:** implement component ([18dceb7](https://github.com/nuxtlabs/ui/commit/18dceb74455ccd6690069b973bcc9f563c78ebb3))
* **Dropdown:** add `hover` mode ([#45](https://github.com/nuxtlabs/ui/issues/45)) ([77149f0](https://github.com/nuxtlabs/ui/commit/77149f0dd454882dd0a271794d80a9a597789923))
* **Icon:** support custom component and emoji ([c1a7629](https://github.com/nuxtlabs/ui/commit/c1a7629e2988886633f989244fc2abfd580c6e1f))
* migrate to `@nuxtjs/tailwindcss` ([#32](https://github.com/nuxtlabs/ui/issues/32)) ([702abf7](https://github.com/nuxtlabs/ui/commit/702abf7a9fd91428d57b5dc95be34ded7f2db9e2))
* **module:** handle variants with dynamic colors ([5a8b078](https://github.com/nuxtlabs/ui/commit/5a8b078d65e32d0d3f5b8dc8df903814abfd5fca))
* **plugins:** clipboard ([#29](https://github.com/nuxtlabs/ui/issues/29)) ([832ffe4](https://github.com/nuxtlabs/ui/commit/832ffe4323c73e43506261c4c1765fd68851d7a0))
* **Popover:** handle hovering mode ([#47](https://github.com/nuxtlabs/ui/issues/47)) ([c431f8b](https://github.com/nuxtlabs/ui/commit/c431f8b4a17880b31eb838059cb03c8e95955c0e))
* **Slideover:** add close button in header ([#65](https://github.com/nuxtlabs/ui/issues/65)) ([2f90ce2](https://github.com/nuxtlabs/ui/commit/2f90ce2319766aed92063bcc9e3c92d7a1b3de99))
* **Slideover:** allow opening from the right side ([#64](https://github.com/nuxtlabs/ui/issues/64)) ([aecfef2](https://github.com/nuxtlabs/ui/commit/aecfef20e6aa7a092741f4ab7988c6b3e04bd706))
* **Slideover:** preset support ([#68](https://github.com/nuxtlabs/ui/issues/68)) ([5b4e4f8](https://github.com/nuxtlabs/ui/commit/5b4e4f864894261349834a5587c9eab4a0738c47))
* **toast:** add aliases for `info` and `warning` notifications ([23deef3](https://github.com/nuxtlabs/ui/commit/23deef325a33bd52f90dbd0a1005131b4314c1fd))
* **toast:** expose `timeout` to alias methods ([#30](https://github.com/nuxtlabs/ui/issues/30)) ([6bd5197](https://github.com/nuxtlabs/ui/commit/6bd51975bfefb963fa59c4a151833db029cfe93a))
### Bug Fixes
* **Avatar:** add missing `watch` import ([cc01af8](https://github.com/nuxtlabs/ui/commit/cc01af8e553bfe50eb8f1a51260db0331dc11a69))
* **AvatarGroup:** pass all avatar props ([723f075](https://github.com/nuxtlabs/ui/commit/723f075c562fa5ed261d02656e7cd5684fa06170))
* **Avatar:** missing `ref` import ([eb41b23](https://github.com/nuxtlabs/ui/commit/eb41b23432f931adaf52c650034e394c6ed9c547))
* **Avatar:** placeholder ([#31](https://github.com/nuxtlabs/ui/issues/31)) ([1bec8d1](https://github.com/nuxtlabs/ui/commit/1bec8d163c9288ecbce5acbc13a71b7a2c43fc5e))
* **Avatar:** prevent boolean src ([da3ed26](https://github.com/nuxtlabs/ui/commit/da3ed26c2ccc65f3b7d68fd07315c7c607e88318))
* **Avatar:** remove gradient support ([ed499b3](https://github.com/nuxtlabs/ui/commit/ed499b3b21097ead948de6c5f4270ffda5dafb6e))
* **Avatar:** truncate placeholder if too long ([#61](https://github.com/nuxtlabs/ui/issues/61)) ([6585bfc](https://github.com/nuxtlabs/ui/commit/6585bfc24a36d9b35bc1f5dcec9394c5aa731ec4))
* **Avatar:** url error handling ([#39](https://github.com/nuxtlabs/ui/issues/39)) ([fb3ff2e](https://github.com/nuxtlabs/ui/commit/fb3ff2e5fa2fd0a86a71da40cf4a7258be0d3899))
* **Button:** wrong config for icon size class ([760da3d](https://github.com/nuxtlabs/ui/commit/760da3d1a81ca06b49e95d30e7794ad3c72acb81))
* **Card:** nullable validator on card roundedClass prop ([c4a40b0](https://github.com/nuxtlabs/ui/commit/c4a40b065c021e3af706ea65a02fdce591f5acb4))
* **Card:** prevent double class ([06b07e2](https://github.com/nuxtlabs/ui/commit/06b07e292e9b29359926273fcacb6d25aa440f10))
* **Card:** prevent empty `sm:` class when `rounded-class` is null ([dd64637](https://github.com/nuxtlabs/ui/commit/dd6463710c58b62a3a5714a5917d885bb668ff55))
* **colors:** hard-code colors as tailwindcss/colors is different ([f67fdb7](https://github.com/nuxtlabs/ui/commit/f67fdb7d2fbe12f696cc02f00800dc0c4d8e5df5))
* **colors:** move primary to safeColors ([3ab0698](https://github.com/nuxtlabs/ui/commit/3ab0698d7f2ce4776ccf4f16e069e0af2ccd38b8))
* **CommandPalette:** add missing import ([ea293ba](https://github.com/nuxtlabs/ui/commit/ea293bae0ca10c19e8a1c021fd0d708933cd6c0b))
* **CommandPaletteGroup:** fail replace on items ([1495ff9](https://github.com/nuxtlabs/ui/commit/1495ff987d4e464540755a4e2b7e72b0b91548dc))
* **CommandPaletteGroup:** invalid spacing when no icon ([cf65b4a](https://github.com/nuxtlabs/ui/commit/cf65b4ab5456f2c5b54968ab011330fc324bad5f))
* **CommandPalette:** options priority ([76ffbf4](https://github.com/nuxtlabs/ui/commit/76ffbf4cf355f3cb36a9d896a4ed6d2a874fe780))
* **CommandPalette:** slice from computed options ([503b9a6](https://github.com/nuxtlabs/ui/commit/503b9a6b5c2e31152e7ea42aa059e595a4a92c17))
* Dropdown and Popover manual padding ([2e09939](https://github.com/nuxtlabs/ui/commit/2e099392f1f9ab926c6ce42146f649496a4723c3))
* **Dropdown:** improve disabled state ([5fb7f10](https://github.com/nuxtlabs/ui/commit/5fb7f102830eb33b207cf3ca0c955f7853df5fbd))
* Hover mode on Dropdown & Popover ([#48](https://github.com/nuxtlabs/ui/issues/48)) ([8bc4902](https://github.com/nuxtlabs/ui/commit/8bc4902078b34c0233c9304c996d38746b14b270))
* **Icon:** missing imports ([c3facb1](https://github.com/nuxtlabs/ui/commit/c3facb1fefa4c9681290d2f88ea2c78a7aeee217))
* **Icon:** name can be an object ([f513ea6](https://github.com/nuxtlabs/ui/commit/f513ea6ca89928982694819abbc8b5f26ddec5ae))
* **Icon:** reload icon when prop name changes ([78021d3](https://github.com/nuxtlabs/ui/commit/78021d385058634658f722ee90db214661ae88e4))
* **input:** background should go into appearance ([33b1176](https://github.com/nuxtlabs/ui/commit/33b1176bdd66e3774e8063e1e2caef4983e7680d))
* **Link:** `exact` handling ([ceedbe0](https://github.com/nuxtlabs/ui/commit/ceedbe0bbdb4cfac0548e3d00ccc2480bd41ec13))
* **Link:** add missing `inactive-class` on `button` and `a` ([035919a](https://github.com/nuxtlabs/ui/commit/035919a545f20b2714cbc21a7efa2c7ae2ad16cb))
* **Link:** handle `isActive` through vue-router ([aef1156](https://github.com/nuxtlabs/ui/commit/aef11562c945fe5e8384705c04948aa5f9db085e))
* **Link:** send correct active ([d4b6599](https://github.com/nuxtlabs/ui/commit/d4b65996ce025795e05d4e2621713f2ca46f855f))
* **Link:** use `exact` ([7737242](https://github.com/nuxtlabs/ui/commit/77372423d824dcfc7a175ccbeb3a6cb8a78a5af9))
* **Modal:** `widthClass` prop and default preset value ([#56](https://github.com/nuxtlabs/ui/issues/56)) ([d980176](https://github.com/nuxtlabs/ui/commit/d980176e03b9853a1c7f26db7a06c238fec96b81))
* **Modal:** move classes to `DialogPanel` ([dfe86f0](https://github.com/nuxtlabs/ui/commit/dfe86f0baf8ee43d4efc6730b8853dbcc322cce1))
* **Modal:** prevent attrs inherit ([850c766](https://github.com/nuxtlabs/ui/commit/850c766fabaef5be27270338b1e76abacae48877))
* **module:** `gradient-avatar` include ([85c8210](https://github.com/nuxtlabs/ui/commit/85c8210edeff59b18d3a46fb56fe8e5b8667a473))
* **module:** import `colors` from preset-mini ([185273f](https://github.com/nuxtlabs/ui/commit/185273f3e91d8a1d175020fe2774a2d948105b87))
* **module:** move colors utils to runtime dir ([93c9fe1](https://github.com/nuxtlabs/ui/commit/93c9fe1c74bc8ba02a6ef8f3bfc8a8b260349c93))
* **module:** parse presets with mjs ext ([b2705fe](https://github.com/nuxtlabs/ui/commit/b2705feedce6a2c957990da8730c800cfd654d46))
* **module:** presets content ([b8dd80d](https://github.com/nuxtlabs/ui/commit/b8dd80d3d37fbf9c02a8a056615deec8ffcd9302))
* **module:** register typography plugin ([bbbe57d](https://github.com/nuxtlabs/ui/commit/bbbe57dedbab96fe08671488a91626f018f19c71))
* **module:** remove safelist on max-w ([358387e](https://github.com/nuxtlabs/ui/commit/358387e3584b571685831171c322e6c98bd80433))
* **module:** resolve runtime dir ([65aa169](https://github.com/nuxtlabs/ui/commit/65aa1690ba584c7c66685ae22968b5e3bb086aff))
* **module:** search in tailwind colors for `primary` and `gray` ([10d89d3](https://github.com/nuxtlabs/ui/commit/10d89d3cd109561138f262c18732d832bae371dc))
* **module:** use `variants` key for safelist ([4c89122](https://github.com/nuxtlabs/ui/commit/4c891225b18efbc039288af489929c73d5245d0b))
* **Notifications:** default value in `useState` ([af566ab](https://github.com/nuxtlabs/ui/commit/af566ab1fa94d9542b0fd5f96010962054342085))
* **Notifications:** inexistant `z-55` ([60c72a2](https://github.com/nuxtlabs/ui/commit/60c72a20d1fcb82734c13721b003cef9222ee61a))
* **package:** update `postbuild:docs` command ([8045c7b](https://github.com/nuxtlabs/ui/commit/8045c7b47e117d1ec5d0eb0a53973743079d9f98))
* **plugins:** error in provides ([46ea467](https://github.com/nuxtlabs/ui/commit/46ea467098441b96329383154e7e60fd8ebbec06))
* **Popover:** add missing `onMounted` import ([656b6e1](https://github.com/nuxtlabs/ui/commit/656b6e1c59d89a82b2c3117c87a57e9c99e621c8))
* **Popover:** add missing `ref` import ([2eb2fea](https://github.com/nuxtlabs/ui/commit/2eb2feab672da15f102a500dfbde036bef96b9e4))
* **popper:** use `$el` after 1.5 upgrade ([9554e80](https://github.com/nuxtlabs/ui/commit/9554e801c26de31205f84459ae7679f714de172a)), closes [/github.com/tailwindlabs/headlessui/discussions/1125#discussioncomment-2299441](https://github.com/nuxtlabs//github.com/tailwindlabs/headlessui/discussions/1125/issues/discussioncomment-2299441)
* **preset:** replace avatar wrapper with `inline-flex` ([45cf898](https://github.com/nuxtlabs/ui/commit/45cf898ec3f03a7cc8631e9fe2adc8bac8bd53e9))
* **presets:** add disabled bg color on nuxt buttons ([e1d79d7](https://github.com/nuxtlabs/ui/commit/e1d79d7fe77e93305c04e7333effc2dad0d9b850))
* **presets:** defu merging ([e034218](https://github.com/nuxtlabs/ui/commit/e0342184febf3dbd355189c0766b20d268fce6ef))
* **presets:** dropdown avatar position ([516e7fa](https://github.com/nuxtlabs/ui/commit/516e7faf8ff0c8fe856da0c12ecae2d6d838b816))
* **presets:** support dark ring-offset-color ([a9f1d93](https://github.com/nuxtlabs/ui/commit/a9f1d937bc18d590a9038b15aa21743a15b48b24))
* **SelectCustom:** add default value to placeholder ([8492e16](https://github.com/nuxtlabs/ui/commit/8492e161dbb16bcece401da41ac83f250be8f68b))
* **SelectCustom:** add missing `listContainerClass` prop ([ae6e8ee](https://github.com/nuxtlabs/ui/commit/ae6e8eec032b8fee5581692dcf9a9f873fc8300c))
* **SelectCustom:** add missing `required` prop ([619f620](https://github.com/nuxtlabs/ui/commit/619f620b7ec9392b67c4ab61fb314dafd241b089))
* **SelectCustom:** add missing `text-sm` class ([eb6fbd9](https://github.com/nuxtlabs/ui/commit/eb6fbd9c4ae9efcc5c608f4e26de857409d219b6))
* **SelectCustom:** add missing bg in list input ([da4e8d5](https://github.com/nuxtlabs/ui/commit/da4e8d5c099dbde3a79d124d7debd590d8da0e58))
* **SelectCustom:** add tabindex -1 to hidden input ([09aed4b](https://github.com/nuxtlabs/ui/commit/09aed4b7529bb40a4b1cf9e28cefcc0ba33e1e33))
* **SelectCustom:** dark mode preset ([1e6ad72](https://github.com/nuxtlabs/ui/commit/1e6ad726441bb1f87c2688cdc203417bb0afe267))
* **SelectCustom:** handle placeholder when value is null ([4e0d23e](https://github.com/nuxtlabs/ui/commit/4e0d23ed3470f79f5c6ab805a7f5edd39594c197))
* **SelectCustom:** icon name in prop only for now ([2e64343](https://github.com/nuxtlabs/ui/commit/2e6434361004e6bc7e6de1e7ed669b719e5352d1))
* **SelectCustom:** improve creatable placeholder ([d413cf7](https://github.com/nuxtlabs/ui/commit/d413cf74d6eed70870c60120c1cebbe0335becd6))
* **SelectCustom:** missing padding on list ([524f841](https://github.com/nuxtlabs/ui/commit/524f8411c591fe68c54580d39c094e82cfe82620))
* **SelectCustom:** move max-height on base ([db39a9c](https://github.com/nuxtlabs/ui/commit/db39a9cdba2a585a4bde6c26c8dae1e0b3c1cd02))
* **SelectCustom:** move wrapper on top of `Listbox` ([8222d05](https://github.com/nuxtlabs/ui/commit/8222d05c15a7cede4f42eeebd2f3e14e01238819))
* **SelectCustom:** move wrapper under `Listbox` ([936d6a5](https://github.com/nuxtlabs/ui/commit/936d6a5fee41fc628f4b4efb2a32d46088316be8))
* **SelectCustom:** prop is `icon` instead of `iconName` ([6da0c28](https://github.com/nuxtlabs/ui/commit/6da0c2801909d27b0318c80f1ef0880150e4c14e))
* **SelectCustom:** remove unused import ([0e0f3e3](https://github.com/nuxtlabs/ui/commit/0e0f3e39d31bd70404600ac7d4b9641335839f3c))
* **Select:** default value handling ([eb00439](https://github.com/nuxtlabs/ui/commit/eb0043914bc832be57e1af6def72574a86c76339))
* **Select:** disable placeholder ([7723704](https://github.com/nuxtlabs/ui/commit/7723704f793eb444b24ee8b9d0a0eb2260b991c0))
* **Select:** normalizedValue handling Object modelValue ([#59](https://github.com/nuxtlabs/ui/issues/59)) ([e419d68](https://github.com/nuxtlabs/ui/commit/e419d68f645a14045519a72427f48015e331859b))
* **Slideover:** remove useless padding ([cf021a5](https://github.com/nuxtlabs/ui/commit/cf021a5888af2d996ccdfd99e693ce6797c7d36a))
* **Textarea:** autoresize ([#43](https://github.com/nuxtlabs/ui/issues/43)) ([ba643d9](https://github.com/nuxtlabs/ui/commit/ba643d9faa3761c336e18bfb4671db6e21e88397))
* **textarea:** autoresize reactivity ([#52](https://github.com/nuxtlabs/ui/issues/52)) ([f0bfe20](https://github.com/nuxtlabs/ui/commit/f0bfe20572b028747ed04f3b185880e4204ea072))
* **toast:** `id` should be a string ([7db0ca5](https://github.com/nuxtlabs/ui/commit/7db0ca50a00db12f9d5b3c9adf4d8ce788fe4d1b))
* **Toggle:** add `v-if` when icon props not defined ([90ff1c0](https://github.com/nuxtlabs/ui/commit/90ff1c0671e53ffe7fbf7b018f077f8d8fbe16cc))
* **Tooltip:** prevent close when hovering ([18c194e](https://github.com/nuxtlabs/ui/commit/18c194e8393c07acce40f2dc588d310b2f915126))
* **useTimer:** lint rule changed ([0e18526](https://github.com/nuxtlabs/ui/commit/0e18526a6fa3e32b2aedd137ca9b57d326ea20d9))
* **utils:** types ([00e0ab3](https://github.com/nuxtlabs/ui/commit/00e0ab39f8da9ffb6fffdd7121a69f3aaa071fd3))
* **VerticalNavigation:** `link.avatar` is now an object ([d733e25](https://github.com/nuxtlabs/ui/commit/d733e25bf01ae083e06b35a1bbd58a8bc87f8c4b))
* **VerticalNavigation:** remove `avatarSize` prop ([f0835cf](https://github.com/nuxtlabs/ui/commit/f0835cf979ff0e4e2fc1e36b1cbd377ee32f9b1b))
### 0.0.2 (2022-02-01)

View File

@@ -1,6 +1,6 @@
# @nuxthq/ui
Components library as a Nuxt3 module using [UnoCSS](https://github.com/antfu/unocss).
Components library as a Nuxt module using [TailwindCSS](https://tailwindcss.com) and [HeadlessUI](https://headlessui.com).
## Installation
@@ -11,7 +11,7 @@ yarn add --dev @nuxthq/ui
Then, register the module in your `nuxt.config.js`:
```js
import { defineNuxtConfig } from 'nuxt3'
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
buildModules: [
@@ -29,43 +29,3 @@ If you want latest updates, please use `@nuxthq/ui-edge` in your `package.json`:
}
}
```
## Options
- `primary`
Define the primary variant. Defaults to `indigo`. You can specify your own object of colors like here:
**Example:**
```js
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
buildModules: [
'@nuxthq/ui'
],
ui: {
primary: 'blue'
}
})
```
- `prefix`
Define the prefix of the imported components. Defaults to `u`.
**Example:**
```js
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
buildModules: [
'@nuxthq/ui'
],
ui: {
prefix: 'tw'
}
})
```

View File

@@ -1,77 +1,43 @@
<template>
<div class="antialiased font-sans">
<nav class="u-bg-white border-b u-border-gray-200 fixed top-0 inset-x-0 z-50">
<UContainer padded>
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<NuxtLink to="/" class="block font-bold text-lg u-text-gray-900">
@nuxthq/ui
</NuxtLink>
</div>
<div>
<Header />
<UseDark v-slot="{ isDark, toggleDark }">
<UButton variant="transparent" :icon="isDark ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark()" />
</UseDark>
</div>
</UContainer>
</nav>
<UContainer class="mt-16">
<div class="lg:grid lg:grid-cols-10 lg:gap-10 lg:relative">
<aside class="lg:flex lg:flex-col lg:relative pb-8 lg:pb-0 lg:sticky lg:top-0 px-4 sm:px-6 lg:px-0 lg:pt-16 lg:-mt-16 lg:self-start lg:col-span-2 lg:overflow-hidden lg:sticky lg:h-screen">
<nav class="overflow-y-auto h-auto pt-8 lg:py-12">
<ul class="space-y-6">
<li v-for="section of sections" :key="section">
<h5 class="mb-3 uppercase tracking-wide font-semibold text-xs u-text-gray-900">
{{ section.label }}
</h5>
<ul class="space-y-1.5">
<li v-for="(link, index) of section.links" :key="index">
<ULink
:to="link.to"
class="relative block text-sm rounded-md"
active-class="text-primary-600"
inactive-class="u-text-gray-500 hover:u-text-gray-700"
exact
>
{{ link.label }}
</ULink>
</li>
</ul>
</li>
</ul>
</nav>
</aside>
<div class="space-y-6 sm:px-6 lg:px-0 lg:col-span-8 lg:py-12">
<NuxtPage />
</div>
</div>
<UContainer>
<NuxtPage />
</UContainer>
<ClientOnly>
<UNotifications />
</ClientOnly>
<DocsSearch />
<UNotifications />
</div>
</template>
<script setup>
import { UseDark } from '@vueuse/components'
<script setup lang="ts">
const colorScheme = usePreferredColorScheme()
const colorMode = useColorMode()
const sections = [
{ label: 'Getting Started', links: [{ label: 'Usage', to: '/' }, { label: 'Examples', to: '/examples' }, { label: 'Migration', to: '/migration' }, { label: 'Dark mode', to: '/dark' }] },
{ label: 'Elements', links: [{ label: 'Avatar', to: '/components/Avatar' }, { label: 'AvatarGroup', to: '/components/AvatarGroup' }, { label: 'Badge', to: '/components/Badge' }, { label: 'Button', to: '/components/Button' }, { label: 'Dropdown', to: '/components/Dropdown' }, { label: 'Icon', to: '/components/Icon' }] },
{ label: 'Feedback', links: [{ label: 'Alert', to: '/components/Alert' }, { label: 'AlertDialog', to: '/components/AlertDialog' }] },
{ label: 'Forms', links: [{ label: 'Checkbox', to: '/components/Checkbox' }, { label: 'Input', to: '/components/Input' }, { label: 'FormGroup', to: '/components/FormGroup' }, { label: 'Radio', to: '/components/Radio' }, { label: 'Select', to: '/components/Select' }, { label: 'Textarea', to: '/components/Textarea' }, { label: 'Toggle', to: '/components/Toggle' }] },
{ label: 'Layout', links: [{ label: 'Card', to: '/components/Card' }, { label: 'Container', to: '/components/Container' }] },
// { label: 'Navigation', links: [{ label: 'Pills', to: '/components/Pills' }, { label: 'Tabs', to: '/components/Tabs' }, { label: 'VerticalNavigation', to: '/components/VerticalNavigation' }] },
{ label: 'Navigation', links: [{ label: 'VerticalNavigation', to: '/components/VerticalNavigation' }] },
{ label: 'Overlays', links: [{ label: 'Modal', to: '/components/Modal' }, { label: 'Notification', to: '/components/Notification' }, { label: 'Popover', to: '/components/Popover' }, { label: 'Tooltip', to: '/components/Tooltip' }] }
]
// Computed
const href = computed(() => colorScheme.value === 'dark' ? '/icon-dark.svg' : '/icon-light.svg')
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
// Head
useHead({
titleTemplate: title => title && title !== 'nuxthq/ui' ? `${title} - nuxthq/ui` : 'nuxthq/ui',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' },
{ key: 'theme-color', name: 'theme-color', content: color }
],
link: [
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' },
{ rel: 'icon', type: 'image/svg+xml', href }
],
htmlAttrs: {
lang: 'en'
},
bodyAttrs: {
class: 'antialiased font-sans text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-900'
}
})
</script>
<style>
html.dark {
background-color: black;
}
</style>

View File

@@ -0,0 +1,48 @@
import type { RouterConfig } from '@nuxt/schema'
function findHashPosition (hash): { el: any, behavior: ScrollBehavior, top: number } {
const el = document.querySelector(hash)
// vue-router does not incorporate scroll-margin-top on its own.
if (el) {
const top = parseFloat(getComputedStyle(el).scrollMarginTop)
return {
el: hash,
behavior: 'smooth',
top
}
}
}
// https://router.vuejs.org/api/#routeroptions
export default <RouterConfig>{
scrollBehavior (to, from, savedPosition) {
const nuxtApp = useNuxtApp()
// If history back
if (savedPosition) {
// Handle Suspense resolution
return new Promise((resolve) => {
nuxtApp.hooks.hookOnce('page:finish', () => {
setTimeout(() => resolve(savedPosition), 50)
})
})
}
// Scroll to heading on click
if (to.hash) {
return new Promise((resolve) => {
if (to.path === from.path) {
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
} else {
nuxtApp.hooks.hookOnce('page:finish', () => {
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
})
}
})
}
// Scroll to top of window
return { top: 0 }
}
}

112
docs/components/Header.vue Normal file
View File

@@ -0,0 +1,112 @@
<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">
<UContainer>
<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 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"
/>
</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>
</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>
<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>
</Dialog>
</TransitionRoot>
</header>
</template>
<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)
}
</script>

5
docs/components/Logo.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M504.908 750H839.476C850.103 750.001 860.542 747.229 869.745 741.963C878.948 736.696 886.589 729.121 891.9 719.999C897.211 710.876 900.005 700.529 900 689.997C899.995 679.465 897.193 669.12 891.873 660.002L667.187 274.289C661.876 265.169 654.237 257.595 645.036 252.329C635.835 247.064 625.398 244.291 614.773 244.291C604.149 244.291 593.711 247.064 584.511 252.329C575.31 257.595 567.67 265.169 562.36 274.289L504.908 372.979L392.581 179.993C387.266 170.874 379.623 163.301 370.42 158.036C361.216 152.772 350.777 150 340.151 150C329.525 150 319.086 152.772 309.883 158.036C300.679 163.301 293.036 170.874 287.721 179.993L8.12649 660.002C2.80743 669.12 0.00462935 679.465 5.72978e-06 689.997C-0.00461789 700.529 2.78909 710.876 8.10015 719.999C13.4112 729.121 21.0523 736.696 30.255 741.963C39.4576 747.229 49.8973 750.001 60.524 750H270.538C353.748 750 415.112 713.775 457.336 643.101L559.849 467.145L614.757 372.979L779.547 655.834H559.849L504.908 750ZM267.114 655.737L120.551 655.704L340.249 278.586L449.87 467.145L376.474 593.175C348.433 639.03 316.577 655.737 267.114 655.737Z" fill="currentColor" />
</svg>
</template>

View File

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

@@ -0,0 +1,33 @@
<template>
<component
:is="to ? NuxtLink : 'div'"
:to="to"
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200 text-sm leading-6 my-5 last:mb-0 font-normal group relative prose-code:bg-gray-200 dark:prose-code:bg-gray-800"
:class="[to ? 'hover:!border-primary-500 dark:hover:!border-primary-400 hover:text-primary-500 dark:hover:text-primary-400 border-dashed' : '']"
>
<UIcon v-if="!!to" name="i-heroicons-link-20-solid" class="w-3 h-3 absolute right-2 top-2 text-gray-400 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
<UIcon v-if="icon" :name="icon" class="w-4 h-4 mr-2 inline-flex items-center align-text-top" :class="color" />
<ContentSlot :use="$slots.default" unwrap="p" />
</component>
</template>
<script setup lang="ts">
const NuxtLink = resolveComponent('NuxtLink')
defineProps({
icon: {
type: String,
default: null
},
color: {
type: String,
default: 'text-primary-500 dark:text-primary-400'
},
to: {
type: String,
default: null
}
})
</script>

View File

@@ -0,0 +1,53 @@
<template>
<div :selected-index="selectedIndex" @change="changeTab">
<div class="flex border border-gray-200 dark:border-gray-700 border-b-0 rounded-t-md overflow-hidden -mb-px">
<div
v-for="(tab, index) in tabs"
:key="index"
as="template"
@click="selectedIndex = index"
>
<button
class="px-4 py-2 focus:outline-none text-sm border-r border-r-gray-200 dark:border-r-gray-700 transition-colors"
tabindex="-1"
:class="[selectedIndex === index ? 'font-medium text-primary-500 dark:text-primary-400 bg-gray-50 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800']"
>
{{ tab.label }}
</button>
</div>
</div>
<div class="[&>div>pre]:!rounded-t-none">
<component :is="selectedTab.component" />
</div>
</div>
</template>
<script setup lang="ts">
const slots = useSlots()
const selectedIndex = ref(0)
// Computed
const tabs = computed(() => slots.default?.().map((slot, index) => {
return {
label: slot.props?.filename || slot.props?.label || `${index}`,
component: slot
}
}) || [])
const selectedTab = computed(() => tabs.value.find((_, index) => index === selectedIndex.value))
// Methods
function changeTab (index) {
selectedIndex.value = index
}
</script>
<script lang="ts">
export default {
inheritAttrs: false
}
</script>

View File

@@ -0,0 +1,184 @@
<template>
<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>
<UCheckbox
v-if="prop.type === 'boolean'"
v-model="componentProps[prop.name]"
:name="prop.name"
appearance="none"
class="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' }"
: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"
autocomplete="off"
:ui="{ custom: '!py-0' }"
@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]">
<component :is="name" v-model="vModel" v-bind="fullProps">
<ContentSlot v-if="$slots.default" :use="$slots.default" />
</component>
</div>
<ContentRenderer :value="ast" class="[&>div>pre]:!rounded-t-none" />
</div>
</template>
<script setup lang="ts">
// @ts-expect-error
import { transformContent } from '@nuxt/content/transformers'
const props = defineProps({
slug: {
type: String,
default: null
},
padding: {
type: Boolean,
default: true
},
props: {
type: Object,
default: () => ({})
},
code: {
type: String,
default: null
},
baseProps: {
type: Object,
default: () => ({})
},
ui: {
type: Object,
default: () => ({})
},
excludedProps: {
type: Array,
default: () => []
},
backgroundClass: {
type: String,
default: 'bg-white dark:bg-gray-900'
}
})
const baseProps = reactive({ ...props.baseProps })
const componentProps = reactive({ ...props.props })
const appConfig = useAppConfig()
const route = useRoute()
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`
const meta = await fetchComponentMeta(name)
// Computed
const ui = computed(() => ({ ...appConfig.ui[camelName], ...props.ui }))
const fullProps = computed(() => ({ ...props.baseProps, ...componentProps }))
const vModel = computed({
get: () => baseProps.modelValue,
set: (value) => {
baseProps.modelValue = value
}
})
const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
if (props.excludedProps.includes(key)) {
return null
}
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
const dottedKey = useKebabCase(key).replaceAll('-', '.')
const keys = useGet(ui.value, dottedKey, {})
let options = typeof keys === 'object' && Object.keys(keys)
if (key.toLowerCase().endsWith('color')) {
options = appConfig.ui.colors
}
return {
type: prop?.type || 'string',
name: key,
label: key === 'modelValue' ? 'value' : useCamelCase(key),
options
}
}).filter(Boolean))
const code = computed(() => {
let code = `\`\`\`html
<${name}`
for (const [key, value] of Object.entries(componentProps)) {
if (value === 'undefined' || value === null) {
continue
}
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
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) {
const lineBreaks = (props.code.match(/\n/g) || []).length
if (lineBreaks > 1) {
code += `>
${props.code}</${name}>`
} else {
code += `>${props.code}</${name}>`
}
} else {
code += ' />'
}
code += `
\`\`\`
`
return code
})
function renderObject (obj: any) {
if (Array.isArray(obj)) {
return `[${obj.map(renderObject).join(', ')}]`
}
if (typeof obj === 'object') {
return `{ ${Object.entries(obj).map(([key, value]) => `${key}: ${renderObject(value)}`).join(', ')} }`
}
if (typeof obj === 'string') {
return `'${obj}'`
}
return obj
}
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify(componentProps)}`, () => transformContent('content:_markdown.md', code.value, {
highlight: {
theme: {
light: 'material-lighter',
dark: 'material-palenight'
}
}
}), { watch: [code] })
</script>

View File

@@ -0,0 +1,22 @@
<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]">
<ContentSlot v-if="$slots.default" :use="$slots.default" />
</div>
<ContentSlot v-if="$slots.code" :use="$slots.code" />
</div>
</template>
<script setup lang="ts">
defineProps({
padding: {
type: Boolean,
default: true
},
backgroundClass: {
type: String,
default: 'bg-white dark:bg-gray-900'
}
})
</script>

View File

@@ -0,0 +1,36 @@
<template>
<ContentRenderer :value="ast" />
</template>
<script setup lang="ts">
// @ts-expect-error
import { transformContent } from '@nuxt/content/transformers'
const props = defineProps({
slug: {
type: String,
default: null
}
})
const appConfig = useAppConfig()
const route = useRoute()
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`
const preset = appConfig.ui[camelName]
const { data: ast } = await useAsyncData(`${name}-preset`, () => transformContent('content:_markdown.md', `
\`\`\`json
${JSON.stringify(preset, null, 2)}
\`\`\`\
`, {
highlight: {
theme: {
light: 'material-lighter',
dark: 'material-palenight'
}
}
}))
</script>

View File

@@ -0,0 +1,55 @@
<template>
<div>
<table class="table-fixed">
<thead>
<tr>
<th class="w-[25%]">
Prop
</th>
<th class="w-[50%]">
Default
</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr v-for="prop in metaProps" :key="prop.name">
<td class="relative flex-shrink-0">
<code>{{ prop.name }}</code><span v-if="prop.required" class="font-bold text-red-500 dark:text-red-400 absolute top-0 ml-1">*</span>
</td>
<td>
<code v-if="prop.default">{{ prop.default }}</code>
</td>
<td>
<a v-if="prop.name === 'ui'" href="#preset">
<code>{{ prop.type }}</code>
</a>
<code v-else class="break-all">
{{ prop.type }}
</code>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
slug: {
type: String,
default: null
}
})
const route = useRoute()
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`
const meta = await fetchComponentMeta(name)
const metaProps = computed(() => useSortBy(meta?.meta?.props || [], [
prop => ['string', 'number', 'boolean', 'any'].indexOf(prop.type)
]))
</script>

View File

@@ -0,0 +1,34 @@
<template>
<div>
<table>
<thead>
<tr>
<th>Slot</th>
</tr>
</thead>
<tbody>
<tr v-for="slot in (meta.meta.slots as any[])" :key="slot.name">
<td class="whitespace-nowrap">
<code>{{ slot.name }}</code>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
slug: {
type: String,
default: null
}
})
const route = useRoute()
const slug = props.slug || route.params.slug[1]
const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}`
const meta = await fetchComponentMeta(name)
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div class="relative overflow-hidden rounded border border-dashed border-gray-400 dark:border-gray-500 opacity-75">
<svg class="absolute inset-0 h-full w-full stroke-gray-900/10 dark:stroke-white/10" fill="none">
<defs>
<pattern
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
x="0"
y="0"
width="10"
height="10"
patternUnits="userSpaceOnUse"
>
<path d="M-3 13 15-5M-5 5l18-18M-1 21 17 3" />
</pattern>
</defs>
<rect stroke="none" fill="url(#pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e)" width="100%" height="100%" />
</svg>
<slot />
</div>
</template>

View File

@@ -0,0 +1,20 @@
<template>
<kbd class="inline-flex items-center justify-center font-sans font-semibold px-1 h-5 min-w-[20px] text-[11px] rounded !my-0 align-text-top ring-1 ring-gray-300 dark:ring-gray-700">
<ClientOnly>
{{ shortcut }}
</ClientOnly>
</kbd>
</template>
<script setup lang="ts">
const props = defineProps({
value: {
type: String,
required: true
}
})
const { metaSymbol } = useShortcuts()
const shortcut = computed(() => props.value === 'meta' ? metaSymbol.value : props.value)
</script>

View File

@@ -0,0 +1,13 @@
<template>
<UCard>
<template #header>
<Placeholder class="h-8" />
</template>
<Placeholder class="h-32" />
<template #footer>
<Placeholder class="h-8" />
</template>
</UCard>
</template>

View File

@@ -0,0 +1,26 @@
<script setup>
const people = [
{ id: 1, label: 'Wade Cooper' },
{ id: 2, label: 'Arlene Mccoy' },
{ id: 3, label: 'Devon Webb' },
{ id: 4, label: 'Tom Cook' },
{ id: 5, label: 'Tanya Fox' },
{ id: 6, label: 'Hellen Schmidt' },
{ id: 7, label: 'Caroline Schultz' },
{ id: 8, label: 'Mason Heaney' },
{ id: 9, label: 'Claudie Smitham' },
{ id: 10, label: 'Emil Schaefer' }
]
const selected = ref([people[3]])
</script>
<template>
<UCommandPalette
v-model="selected"
multiple
nullable
:groups="[{ key: 'people', commands: people }]"
:fuse="{ resultLimit: 6 }"
/>
</template>

View File

@@ -0,0 +1,46 @@
<script setup>
const router = useRouter()
const commandPaletteRef = ref()
const users = [
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } },
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' } },
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' } }
]
const actions = [
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file') },
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder') },
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag') },
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label') }
]
const groups = computed(() => commandPaletteRef.value?.query
? [{
key: 'users',
commands: users
}]
: [{
key: 'recent',
label: 'Recent searches',
commands: users.slice(0, 1)
}, {
key: 'actions',
commands: actions
}])
function onSelect (option) {
if (option.click) {
option.click()
} else if (option.to) {
router.push(option.to)
} else if (option.href) {
window.open(option.href, '_blank')
}
}
</script>
<template>
<UCommandPalette ref="commandPaletteRef" :groups="groups" @update:model-value="onSelect" />
</template>

View File

@@ -0,0 +1,33 @@
<script setup>
const open = ref(false)
const people = [
{ id: 1, label: 'Wade Cooper' },
{ id: 2, label: 'Arlene Mccoy' },
{ id: 3, label: 'Devon Webb' },
{ id: 4, label: 'Tom Cook' },
{ id: 5, label: 'Tanya Fox' },
{ id: 6, label: 'Hellen Schmidt' },
{ id: 7, label: 'Caroline Schultz' },
{ id: 8, label: 'Mason Heaney' },
{ id: 9, label: 'Claudie Smitham' },
{ id: 10, label: 'Emil Schaefer' }
]
const selected = ref([])
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<UModal v-model="open">
<UCommandPalette
v-model="selected"
multiple
nullable
:groups="[{ key: 'people', commands: people }]"
/>
</UModal>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UContainer>
<Placeholder class="h-32" />
</UContainer>
</template>

View File

@@ -0,0 +1,34 @@
<script setup>
const { x, y } = useMouse()
const isOpen = ref(false)
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
function openContextMenu () {
const top = unref(y)
const left = unref(x)
virtualElement.value.getBoundingClientRect = () => ({
width: 0,
height: 0,
top,
left
})
isOpen.value = true
}
</script>
<template>
<div class="w-full" @contextmenu.prevent="openContextMenu">
<Placeholder class="h-20 w-full flex items-center justify-center">
Right click here
</Placeholder>
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" width-class="w-48">
<div class="p-4">
Menu
</div>
</UContextMenu>
</div>
</template>

View File

@@ -0,0 +1,31 @@
<script setup>
const items = [
[{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
}], [{
label: 'Edit',
icon: 'i-heroicons-pencil-square-20-solid'
}, {
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'
}]
]
</script>
<template>
<UDropdown :items="items" :popper="{ placement: 'bottom-start' }">
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
</UDropdown>
</template>

View File

@@ -0,0 +1,15 @@
<script setup>
const open = ref(false)
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<UModal v-model="open">
<div class="p-4">
<Placeholder class="h-48" />
</div>
</UModal>
</div>
</template>

View File

@@ -0,0 +1,23 @@
<script setup>
const open = ref(false)
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<UModal v-model="open">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<Placeholder class="h-8" />
</template>
<Placeholder class="h-32" />
<template #footer>
<Placeholder class="h-8" />
</template>
</UCard>
</UModal>
</div>
</template>

View File

@@ -0,0 +1,15 @@
<script setup>
const toast = useToast()
const actions = ref([{
label: 'Action 1',
click: () => alert('Action 1 clicked!')
}, {
label: 'Action 2',
click: () => alert('Action 2 clicked!')
}])
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'With actions', actions })" />
</template>

View File

@@ -0,0 +1,7 @@
<script setup>
const toast = useToast()
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'Hello world!' })" />
</template>

View File

@@ -0,0 +1,11 @@
<script setup>
const toast = useToast()
function onCallback () {
alert('Notification expired!')
}
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'Expires soon...', timeout: 1000, callback: onCallback })" />
</template>

View File

@@ -0,0 +1,11 @@
<script setup>
const toast = useToast()
function onClick () {
alert('Clicked!')
}
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: onClick })" />
</template>

View File

@@ -0,0 +1,11 @@
<template>
<UPopover>
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
<template #panel>
<div class="p-4">
<Placeholder class="h-20 w-48" />
</div>
</template>
</UPopover>
</template>

View File

@@ -0,0 +1,9 @@
<script setup>
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref()
</script>
<template>
<USelectMenu v-model="selected" :options="people" />
</template>

View File

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

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="font-medium truncate">{{ selected.join(', ') }}</span>
<span v-else class="block truncate text-gray-400 dark:text-gray-500">Select people</span>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,41 @@
<script setup>
const people = [{
id: 'benjamincanac',
label: 'benjamincanac',
href: 'https://github.com/benjamincanac',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
},
{
id: 'Atinux',
label: 'Atinux',
href: 'https://github.com/Atinux',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
},
{
id: 'smarroufin',
label: 'smarroufin',
href: 'https://github.com/smarroufin',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
},
{
id: 'nobody',
label: 'Nobody',
icon: 'i-heroicons-user-circle'
}]
const selected = ref(people[0])
</script>
<template>
<USelectMenu v-model="selected" :options="people">
<template #label>
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4" />
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" />
{{ selected.label }}
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,15 @@
<script setup>
const open = ref(false)
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<USlideover v-model="open">
<div class="p-4 h-full">
<Placeholder class="w-full h-full" />
</div>
</USlideover>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UTooltip text="Tooltip">
<UButton color="gray" label="Button" />
</UTooltip>
</template>

View File

@@ -0,0 +1,24 @@
<script setup>
const links = [{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
}, {
label: 'Installation',
icon: 'i-heroicons-home',
to: '/getting-started/installation'
}, {
label: 'Vertical Navigation',
icon: 'i-heroicons-chart-bar',
to: '/navigation/vertical-navigation'
}, {
label: 'Command Palette',
icon: 'i-heroicons-command-line',
to: '/navigation/command-palette'
}]
</script>
<template>
<UVerticalNavigation :links="links" />
</template>

View File

@@ -0,0 +1,71 @@
<script lang="ts">
import { defineComponent } from '#imports'
export default defineComponent({
props: {
code: {
type: String,
default: ''
},
language: {
type: String,
default: null
},
filename: {
type: String,
default: null
},
highlights: {
type: Array as () => number[],
default: () => []
},
meta: {
type: String,
default: null
}
},
setup (props) {
const clipboard = useCopyToClipboard({ timeout: 2000 })
const icon = ref('i-heroicons-clipboard-document')
function copy () {
clipboard.copy(props.code, { title: 'Copied to clipboard!' })
icon.value = 'i-heroicons-clipboard-document-check'
setTimeout(() => {
icon.value = 'i-heroicons-clipboard-document'
}, 2000)
}
return {
icon,
copy
}
}
})
</script>
<template>
<div class="group relative" :class="`language-${language}`">
<UButton
:icon="icon"
variant="link"
class="absolute right-3 top-3 opacity-0 group-hover:opacity-100 transition-opacity z-[1]"
size="xs"
tabindex="-1"
@click="copy"
/>
<span v-if="filename" class="text-gray-400 dark:text-gray-500 absolute right-3 bottom-3 text-sm group-hover:opacity-0 transition-opacity">{{ filename }}</span>
<slot />
</div>
</template>
<style>
pre code .line {
display: block;
min-height: 1rem;
}
</style>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
defineProps<{ id: string }>()
</script>
<template>
<h2 :id="id" class="scroll-mt-[161px] lg:scroll-mt-[112px]">
<NuxtLink :href="`#${id}`" class="group">
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
</div>
<slot />
</NuxtLink>
</h2>
</template>

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

@@ -0,0 +1,69 @@
<script setup>
const commandPaletteRef = ref()
const { navigation } = useContent()
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
const groups = computed(() => navigation.value.map(item => ({
key: item._path,
label: item.title,
commands: files.value.filter(file => file._path.startsWith(item._path)).map(file => ({
id: file._id,
icon: 'i-heroicons-document',
title: file.navigation?.title || file.title,
category: item.title,
to: file._path
}))
})))
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 ui = {
wrapper: 'flex flex-col flex-1 min-h-0 bg-gray-50 dark:bg-gray-800',
input: {
wrapper: 'relative flex items-center mx-3 py-3',
base: 'w-full rounded border-2 border-primary-500 placeholder-gray-400 dark:placeholder-gray-500 focus:border-primary-500 focus:outline-none focus:ring-0 h-14 text-lg bg-white dark:bg-gray-900',
icon: 'pointer-events-none absolute left-3 h-6 w-6 text-primary-500 dark:text-primary-400'
},
group: {
wrapper: 'p-3 relative',
label: '-mx-3 px-3 -mt-4 mb-2 py-1 text-sm font-semibold text-primary-500 dark:text-primary-400 font-semibold sticky top-0 bg-gray-50 dark:bg-gray-800 z-10',
container: 'space-y-1',
command: {
base: 'flex justify-between select-none items-center rounded px-2 py-4 gap-2 relative font-medium text-sm group shadow',
active: 'bg-primary-500 dark:bg-primary-400 text-white',
inactive: 'bg-white dark:bg-gray-900',
label: 'flex flex-col min-w-0',
suffix: 'text-xs',
icon: {
base: 'flex-shrink-0 w-6 h-6',
active: 'text-white',
inactive: 'text-gray-400 dark:text-gray-500'
}
}
},
empty: {
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',
icon: 'w-12 h-12 mx-auto text-gray-400 dark:text-gray-500 mb-4'
}
}
</script>
<template>
<UCommandPalette
ref="commandPaletteRef"
:groups="groups"
:ui="ui"
:close="close"
:empty="empty"
command-attribute="title"
:fuse="{
fuseOptions: { keys: ['title', 'category'] },
}"
placeholder="Search docs"
/>
</template>

View File

@@ -0,0 +1,57 @@
<script setup>
const commandPaletteRef = ref()
const suggestions = [
{ id: 'linear', label: 'Linear', icon: 'i-simple-icons-linear' },
{ id: 'figma', label: 'Figma', icon: 'i-simple-icons-figma' },
{ id: 'slack', label: 'Slack', icon: 'i-simple-icons-slack' },
{ id: 'youtube', label: 'YouTube', icon: 'i-simple-icons-youtube' },
{ id: 'github', label: 'GitHub', icon: 'i-simple-icons-github' }
]
const commands = [
{ id: 'clipboard-history', label: 'Clipboard History', icon: 'i-heroicons-clipboard', click: () => alert('New file') },
{ id: 'import-extension', label: 'Import Extension', icon: 'i-heroicons-wrench-screwdriver', click: () => alert('New folder') },
{ id: 'manage-extensions', label: 'Manage Extensions', icon: 'i-heroicons-wrench-screwdriver', click: () => alert('Add hashtag') }
]
const groups = [{
key: 'suggestions',
label: 'Suggestions',
inactive: 'Application',
commands: suggestions
}, {
key: 'commands',
label: 'Commands',
inactive: 'Command',
commands
}]
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'
},
group: {
label: 'px-2 my-2 text-xs font-semibold text-gray-500 dark:text-gray-400',
command: {
base: 'flex justify-between select-none cursor-default items-center rounded-md px-2 py-2 gap-2 relative',
active: 'bg-gray-200 dark:bg-gray-700/50 text-gray-900 dark:text-white',
container: 'flex items-center gap-3 min-w-0',
icon: {
base: 'flex-shrink-0 w-5 h-5',
active: 'text-gray-900 dark:text-white',
inactive: 'text-gray-400 dark:text-gray-500'
},
avatar: {
size: '2xs'
}
}
}
}
</script>
<template>
<UCommandPalette ref="commandPaletteRef" :groups="groups" icon="" :ui="ui" placeholder="Search for apps and commands" />
</template>

View File

@@ -0,0 +1,33 @@
<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]"
>
<div class="relative">
<div class="sticky top-0 pointer-events-none">
<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-1 ml-auto -my-1">
<Shortcut value="meta" />
<Shortcut value="K" />
</div>
</UButton>
</div>
<div class="h-8 bg-gradient-to-b from-white dark:from-gray-900" />
</div>
<DocsAsideLinks />
</div>
</aside>
</template>
<script setup lang="ts">
const { isSearchModalOpen } = useDocs()
</script>

View File

@@ -0,0 +1,31 @@
<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>
<UVerticalNavigation
:links="mapContentLinks(group.children)"
class="mt-1"
:ui="{
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
spacing: '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',
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'
}"
/>
</div>
</div>
</template>
<script setup lang="ts">
import type { NavItem } from '@nuxt/content/dist/runtime/types'
const { navigation } = useContent() as { navigation: NavItem[] }
function mapContentLinks (links: NavItem[]) {
return links?.map(link => ({ label: link.title, icon: link.icon, to: link._path })) || []
}
</script>

View File

@@ -0,0 +1,37 @@
<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) }}
</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">
<UButton
v-if="page.headlessui"
:label="page.headlessui.label"
:to="page.headlessui.to"
icon="i-simple-icons-headlessui"
color="white"
/>
<UButton
v-if="page.github"
label="GitHub"
icon="i-simple-icons-github"
color="white"
:to="`https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/${page._dir}/U${page.title}.vue`"
/>
</div>
</div>
<p v-if="page.description" class="mt-4 text-lg">
{{ page.description }}
</p>
</header>
</template>
<script setup lang="ts">
const { page } = useContent()
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="flex items-center justify-between">
<UButton v-if="prev" :label="prev.navigation?.title || prev.title" :to="prev._path" icon="i-heroicons-arrow-small-left-20-solid" color="white" />
<span v-else>&nbsp;</span>
<UButton v-if="next" :label="next.navigation?.title || next.title" :to="next._path" trailing-icon="i-heroicons-arrow-small-right-20-solid" color="white" />
</div>
</template>
<script setup lang="ts">
const { prev, next } = useContent()
</script>

View File

@@ -0,0 +1,172 @@
<template>
<UModal
v-model="isSearchModalOpen"
:ui="{
spacing: 'sm:p-4',
rounded: 'sm:rounded-lg',
width: 'sm:max-w-3xl',
height: 'h-screen sm:h-[28rem]'
}"
>
<UCommandPalette
ref="commandPaletteRef"
:groups="groups"
command-attribute="title"
:fuse="{
fuseOptions: { ignoreLocation: true, includeMatches: true, minMatchCharLength: 2, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
resultLimit: 10
}"
@update:model-value="onSelect"
@close="isSearchModalOpen = false"
/>
</UModal>
</template>
<script setup lang="ts">
import type { Command } from '../../../src/runtime/types'
const { navigation } = useContent()
const router = useRouter()
const { usingInput } = useShortcuts()
const { isSearchModalOpen } = useDocs()
const commandPaletteRef = ref<HTMLElement & { query: Ref<string>, results: { item: Command }[] }>()
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
// Computed
const defaultGroups = computed(() => navigation.value.map(item => ({
key: item._path,
label: item.title,
commands: files.value.filter(file => file._path.startsWith(item._path)).map(file => ({
id: file._id,
title: file.navigation?.title || file.title,
to: file._path,
suffix: file.description,
icon: file.icon
}))
})))
const queryGroups = computed(() => navigation.value.map(item => ({
key: item._path,
label: item.title,
commands: files.value.filter(file => file._path.startsWith(item._path)).flatMap((file) => {
return [{
id: file._id,
title: file.navigation?.title || file.title,
to: file._path,
description: file.description,
icon: file.icon
},
// @ts-ignore
...Object.entries(groupByHeading(file.body.children)).map(([hash, { title, children }]) => ({
id: `${file._path}${hash}`,
title,
prefix: `${file.navigation?.title || file.title} ->`,
prefixClass: 'text-gray-700 dark:text-gray-200',
to: `${file._path}${hash}`,
children: concatChildren(children),
icon: file.icon
}))]
})
})))
const groups = computed(() => commandPaletteRef.value?.query ? queryGroups.value : defaultGroups.value)
// avoid conflicts between multiple meta_k shortcuts
const canToggleModal = computed(() => isSearchModalOpen.value || !usingInput.value)
// Methods
function remapChildren (children: any[]) {
return children?.map((grandChild) => {
if (['code-inline', 'em', 'a', 'strong'].includes(grandChild.tag)) {
return { type: 'text', value: grandChild.children.find(child => child.type === 'text')?.value || '' }
}
return grandChild
})
}
function concatChildren (children: any[]) {
return children.map((child) => {
if (['alert'].includes(child.tag)) {
child.children = concatChildren(child.children)
}
if (child.tag === 'p') {
child.children = remapChildren(child.children)
child.children = child.children?.reduce((acc, grandChild) => {
if (grandChild.type === 'text') {
if (acc.length && acc[acc.length - 1].type === 'text') {
acc[acc.length - 1].value += grandChild.value
} else {
acc.push(grandChild)
}
} else {
acc.push(grandChild)
}
return acc
}, [])
}
if (['style'].includes(child.tag)) {
return null
}
return child
})
}
function groupByHeading (children: any[]) {
const groups = {} // grouped by path
let hash = '' // file.page with potential `#anchor` concat
let title: string | null
for (const node of children) {
// if heading found, udpate current path
if (['h2', 'h3'].includes(node.tag)) {
// find heading text value
title = node.children?.find(child => child.type === 'text')?.value
if (title) {
hash = `#${node.props.id}`
}
}
// push to existing/new group based on path
if (groups[hash]) {
groups[hash].children.push(node)
} else {
groups[hash] = { children: [node], title }
}
}
return groups
}
function onSelect (option) {
isSearchModalOpen.value = false
if (option.click) {
option.click()
} else if (option.to) {
router.push(option.to)
} else if (option.href) {
window.open(option.href, '_blank')
}
}
// Shortcuts
defineShortcuts({
meta_k: {
usingInput: true,
whenever: [canToggleModal],
handler: () => {
isSearchModalOpen.value = !isSearchModalOpen.value
}
},
escape: {
usingInput: true,
whenever: [isSearchModalOpen],
handler: () => { isSearchModalOpen.value = false }
}
})
</script>

View File

@@ -0,0 +1,19 @@
<template>
<div v-if="toc" class="sticky top-16 bg-white/75 dark:bg-gray-900/75 backdrop-blur group lg:self-start -mx-4 sm:-mx-6 lg:mx-0 px-4 sm:px-6 lg:pl-8 lg:pr-0">
<div class="py-3 lg:py-8 border-b border-dashed border-gray-200 dark:border-gray-800 lg:border-0">
<button class="flex items-center gap-2" tabindex="-1" @click="isTocOpen = !isTocOpen">
<span class="text-sm text-slate-900 font-semibold text-sm leading-6 dark:text-slate-100 truncate">Table of Contents</span>
<UIcon name="i-heroicons-chevron-right-20-solid" class="lg:hidden w-4 h-4 transition-transform duration-100 transform text-gray-400 dark:text-gray-500" :class="[isTocOpen ? 'rotate-90' : 'rotate-0']" />
</button>
<DocsTocLinks class="mt-2 lg:mt-4" :links="toc.links" :class="[isTocOpen ? 'lg:block' : 'hidden lg:block']" />
</div>
</div>
</template>
<script setup lang="ts">
const { toc } = useContent()
const isTocOpen = ref(false)
</script>

View File

@@ -0,0 +1,49 @@
<template>
<ul>
<li v-for="link in links" :key="link.text" :class="{ 'ml-3': link.depth === 3 }">
<a
:href="`#${link.id}`"
class="block py-1 font-medium text-sm"
:class="[activeHeadings.includes(link.id) ? 'text-primary-500 dark:text-primary-400' : 'hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300']"
@click.prevent="scrollToHeading(link.id)"
>
{{ link.text }}
</a>
<DocsTocLinks v-if="link.children" :links="link.children" />
</li>
</ul>
</template>
<script setup lang="ts">
import type { TocLink } from '@nuxt/content/dist/runtime/types'
defineProps({
links: {
type: Array as PropType<TocLink[]>,
default: () => []
}
})
const emit = defineEmits(['move'])
const route = useRoute()
const router = useRouter()
const { activeHeadings, updateHeadings } = useScrollspy()
watch(() => route.path, () => {
setTimeout(() => {
if (process.client) {
updateHeadings([
...document.querySelectorAll('h2'),
...document.querySelectorAll('h3')
])
}
}, 300)
}, { immediate: true })
const scrollToHeading = (id: string) => {
router.push(`#${id}`)
emit('move', id)
}
</script>

View File

@@ -0,0 +1,19 @@
const useComponentsMetaState = () => useState('components-meta', () => ({}))
export async function fetchComponentMeta (name: string) {
const state = useComponentsMetaState()
if (state.value[name]?.then) {
await state.value[name]
return state.value[name]
}
if (state.value[name]) { return state.value[name] }
// Store promise to avoid multiple calls
state.value[name] = $fetch(`/api/component-meta/${name}`).then((meta) => {
state.value[name] = meta
})
await state.value[name]
return state.value[name]
}

View File

@@ -0,0 +1,11 @@
import { createSharedComposable } from '@vueuse/core'
const _useDocs = () => {
const isSearchModalOpen = ref(false)
return {
isSearchModalOpen
}
}
export const useDocs = createSharedComposable(_useDocs)

View File

@@ -0,0 +1,37 @@
/**
* Scrollspy allows you to watch visible headings in a specific page.
* Useful for table of contents live style updates.
*/
export const useScrollspy = () => {
const observer = ref() as Ref<IntersectionObserver>
const visibleHeadings = ref([]) as Ref<string[]>
const activeHeadings = ref([]) as Ref<string[]>
const observerCallback = (entries: IntersectionObserverEntry[]) =>
entries.forEach((entry) => {
const id = entry.target.id
if (entry.isIntersecting) { visibleHeadings.value.push(id) } else { visibleHeadings.value = visibleHeadings.value.filter(t => t !== id) }
})
const updateHeadings = (headings: Element[]) =>
headings.forEach((heading) => {
observer.value.observe(heading)
})
watch(visibleHeadings, (val, oldVal) => {
if (val.length === 0) { activeHeadings.value = oldVal } else { activeHeadings.value = val }
})
// Create intersection observer
onBeforeMount(() => (observer.value = new IntersectionObserver(observerCallback)))
// Destroy it
onBeforeUnmount(() => observer.value?.disconnect())
return {
visibleHeadings,
activeHeadings,
updateHeadings
}
}

View File

@@ -0,0 +1,62 @@
---
title: 'nuxthq/ui'
description: 'Components library as a Nuxt3 module using TailwindCSS based on TailwindUI.'
navigation:
title: Installation
---
## Installation
1. Install `@nuxthq/ui` dependency to your project:
::code-group
```bash [yarn]
yarn add -D @nuxthq/ui
```
```bash [npm]
npm install -D @nuxthq/ui
```
```sh [pnpm]
pnpm i -D @nuxthq/ui
```
::
2. Add it to your `modules` section in your `nuxt.config`:
```ts [nuxt.config]
export default defineNuxtConfig({
modules: ['@nuxthq/ui']
})
```
::alert
That's it! You can now use all the components and composables in your Nuxt app ✨
::
## Options
| Key | Default | Description |
| ------------------------ | ---------------------- | ------------------------------------------------ |
| `prefix` | `u` | Define the prefix of the imported components. |
| `global` | `false` | Expose components globally. |
| `icons` | `['heroicons']` | Icon collections to load. |
## Edge channel
To use the latest updates pushed on the [`dev`](https://github.com/nuxtlabs/ui/tree/dev) branch, you can use `@nuxthq/ui-edge`.
Update your `package.json` to the following:
```json [package.json]
{
"devDependencies": {
"@nuxthq/ui": "npm:@nuxthq/ui-edge@latest"
}
}
```
Then run `npm install` or `yarn install`.

View File

@@ -0,0 +1 @@
## Overview

View File

@@ -0,0 +1,7 @@
## Overview
## Composables
## `defineShortcuts`
## `useShortcuts`

View File

@@ -0,0 +1,152 @@
---
navigation: false
---
## Breaking Changes
Classes to invert dark mode like `u-text-gray-900` have been removed.
- Components now have a `ui` prop to override the entire preset instead of individual props
- Components prop `popperOptions` has been renamed to `popper`
- `Alert`, `AlertDialog`, `Tabs` and `Pills` components have been removed
### `Avatar`
- `wrapperClass`, `backgroundClass`, `placeholderClass` and `roundedClass` props have been removed in favor of `ui`
- `rounded` prop is now a class defaulting to `rounded-full` instead of a boolean prop, can be overriden through `ui.avatar.rounded`
- `chip` prop is now `chipVariant`
- `ui.avatar.size` and `ui.avatar.chip.size` `xxs` and `xxxs` have been renamed respectively to `2xs` and `3xs`
### `AvatarGroup`
- `ringClass` and `marginClass` props have been removed in favor of `ui`
- `group` prop has been removed in favor of slots
### `Badge`
- `baseClass` prop has been removed in favor of `ui`
- `rounded` prop is now a class defaulting to `rounded-md` instead of a boolean prop, can be overriden through `ui.badge.rounded`
- `color` prop has been added to change the color scheme of the badge
- `variant` prop is now the variant instead of the color
- `font-medium` has been moved from `ui.badge.base` to `ui.badge.font`
### `Button`
- `customClass` prop have been removed
- `baseClass`, `iconBaseClass` and `roundedClass` props have been removed in favor of `ui`
- `leadingIconClass` and `trailingIconClass` props have been removed
- `rounded` prop is now a class defaulting to `rounded-md` instead of a boolean prop, can be overriden through `ui.button.rounded`
- `color` prop has been added to change the color scheme of the badge
- `variant` prop is now the variant instead of the color
- `labelCompact` and `compact` props have been removed entirely alongside preset `ui.button.compact` and `ui.button.icon.leading.compactSpacing` and `ui.button.icon.trailing.compactSpacing`
- `padded` prop has been added to remove padding
- `ui.button.size.xxs` has been renamed to `ui.button.size.2xs`
- `ui.button.size.2xl` has been introduced
- `ui.button.gap` has been introduced to replace margins defined in `ui.button.icon.leading.spacing` and `ui.button.icon.trailing.spacing`
- `ui.button.icon.leading.spacing` and `ui.button.icon.trailing.spacing` that added negative margin to icons have been removed to keep consitency when surcharging a button through default slot (code has only been commented for now)
- `font-medium` has been moved from `ui.button.base` to `ui.button.font`
### `ButtonGroup`
- New component
### `Dropdown`
- `wrapperClass`, `containerClass`, `widthClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass`, `divideClass`, `baseClass`, `transitionClass`, `groupClass`, `itemBaseClass`, `itemActiveClass`, `itemInactiveClass`, `itemDisabledClass`, `itemIconClass`, `itemAvatarClass` and `itemShortcutsClass` props have been removed in favor of `ui`
- preset has been updated to improve dark mode
### `Card`
- `baseClass`, `backgroundClass`, `borderColorClass`, `shadowClass`, `ringClass`, `roundedClass`, `bodyClass`, `bodyBackgroundClass`, `headerClass`, `headerBackgroundClass`, `footerClass`, `footerBackgroundClass` props have been removed in favor of `ui`
- `rounded` prop is now a class defaulting to `rounded-lg` instead of a boolean prop, can be overriden through `ui.avatar.rounded`
- `padded` prop has been removed, use `ui.rounded = 'sm:rounded-lg'` instead when false
- `ui.card.border` has been removed in favor of `ui.card.divide`
- `ui.card.header` & `ui.card.footer` are now `{ spacing: '', background: '' }`
### `Container`
- `constrainedClass` prop has been removed in favor of `ui`
- `ui.container.base` and `ui.container.spacing` have been added
- `padded` prop has been removed, use `ui.spacing = 'sm:px-6 lg:px-8'` instead when false
- `constrained` prop has been removed, use `ui.constrained = ''` instead when false
### `Input`
- `wrapperClass`, `baseClass`, `iconBaseClass` and `customClass` props have been removed in favor of `ui`
### `FormGroup`
- Renamed to `InputGroup`
- `wrapperClass`, `containerClass`, `labelClass`, `labelWrapperClass`, `descriptionClass`, `requiredClass` and `hintClass` props have been removed in favor of `ui`
### `Textarea`
- `wrapperClass`, `baseClass` and `customClass` props have been removed in favor of `ui`
- `resize` is now false by default
### `Select`
- `wrapperClass`, `baseClass`, `iconBaseClass` and `customClass` props have been removed in favor of `ui`
### `SelectCustom`
- Renamed to `SelectMenu`
- `placeholder` prop is now `null` by default
- `nullable` prop has been removed
- `textAttribute` has been renamed to `optionAttribute` and now defaults to `label`
- `wrapperClass`, `baseClass`, `iconBaseClass`, `customClass`, `listBaseClass`, `listContainerClass`, `listWidthClass`, `listInputClass`, `listTransitionClass`, `listOptionBaseClass`, `listOptionContainerClass`, `listOptionActiveClass`, `listOptionInactiveClass`, `listOptionSelectedClass`, `listOptionUnselectedClass`, `listOptionDisabledClass`, `listOptionEmptyClass`, `listOptionIcon`, `listOptionIconBaseClass`, `listOptionIconActiveClass`, `listOptionIconInactiveClass` and `listOptionIconSizeClass` props have been removed in favor of `ui`
- `ui.selectCustom.list` has been moved to the root of `ui.selectMenu`, the component now uses `ui.select` to render the default slot
### `Radio`
- `wrapperClass`, `baseClass`, `labelClass`, `requiredClass`, `helpClass` and `customClass` props have been removed in favor of `ui`
### `Checkbox`
- `wrapperClass`, `baseClass`, `labelClass`, `requiredClass`, `helpClass` and `customClass` props have been removed in favor of `ui`
### `Toggle`
- `baseClass`, `activeClass`, `inactiveClass`, `containerBaseClass`, `containerActiveClass`, `containerInactiveClass`, `iconBaseClass`, `iconActiveClass`, `iconInactiveClass`, `iconOnClass` and `iconOffClass` props have been removed in favor of `ui`
### `CommandPalette`
- `inputCloseIcon` and `emptyIcon` props have been removed in favor of `ui`
- `inputIcon` prop has been renamed to `icon`
- `inputPlaceholder` prop has been renamed to `placeholder`
- `options` prop has been renamed to `fuse` to follow the `popper` and `ui` props convention
### `Modal`
- `wrapperClass`, `innerClass`, `containerClass`, `baseClass`, `backgroundClass`, `overlayBackgroundClass`, `overlayTransitionClass`, `shadowClass`, `ringClass`, `roundedClass`, `widthClass` and `transitionClass` props have been removed in favor of `ui`
- `innerStyle` prop has been removed
- `#header` and `#footer` slots have been removed
### `Slideover`
- `wrapperClass`, `baseClass`, `backgroundClass`, `overlayBackgroundClass`, `overlayTransitionClass`, `widthClass`, `headerClass` and `transitionClass` props have been removed in favor of `ui`
- `#header` slot has been removed
### `Popover`
- `wrapperClass`, `containerClass`, `widthClass`, `baseClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass` and `transitionClass` props have been removed in favor of `ui`
### `Tooltip`
- `wrapperClass`, `containerClass`, `baseClass`, `widthClass`, `backgroundClass`, `shadowClass`, `ringClass`, `roundedClass`, `shortcutsClass` and `transitionClass` props have been removed in favor of `ui`
### `ContextMenu`
- `wrapperClass`, `containerClass`, `widthClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass`, `baseClass` and `transitionClass`
### `Notification`
- `backgroundClass`, `shadowClass`, `ringClass`, `roundedClass`, `transitionClass`, `customClass` and `iconBaseClass` props have been removed in favor of `ui`
- `type` prop has been removed
- `ui.notification.type` and `ui.notification.icon.color` have been removed
- `ui.notification.close.icon.name` has been moved to `ui.notification.default.closeIcon`
### `useToast`
- `addNotification` and `removeNotification` have been renamed to `add` and `remove`
- `success`, `info`, `warning` and `error` methods have been removed as `type` disappeared from `Notification`

View File

@@ -0,0 +1,100 @@
---
github: true
---
## Usage
::component-card
---
props:
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
alt: 'Avatar'
---
::
### Size
Use the `size` prop to change the size of the Avatar.
::component-card
---
props:
size: 'md'
baseProps:
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
alt: 'Avatar'
---
::
### Chip
Use the `chipColor`, `chipVariant` and `chipPosition` props to display a chip on the Avatar.
::component-card
---
props:
chipColor: 'primary'
chipVariant: 'solid'
chipPosition: 'top-right'
baseProps:
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
alt: 'Avatar'
---
::
### Placeholder
If there is an error loading the `src` of the avatar or `src` is null a background placeholder will be displayed, customizable in `ui.avatar.background`.
If there's an `alt` prop initials will be displayed on top of the background, customizable in `ui.avatar.placeholder`.
::component-card
---
props:
alt: 'Benjamin Canac'
---
::
### Group
To stack avatars as a group, use the `AvatarGroup` component.
- To limit the amount of avatars to show, use the `max` prop. It'll truncate the avatars and show a "+X" label (where X is the remaining avatars)
- To size all the avatars equally, pass the `size` prop
- To adjust the spacing or the ring between avatars, customize with `ui.avatarGroup.margin` or `ui.avatarGroup.ring`
::component-card{slug="AvatarGroup"}
---
props:
size: 'md'
max: 2
ui:
size:
'3xs': ''
'2xs': ''
xs: ''
sm: ''
md: ''
lg: ''
xl: ''
'2xl': ''
'3xl': ''
code: |
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" alt="benjamincanac" />
<UAvatar src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Atinux" />
<UAvatar src="https://avatars.githubusercontent.com/u/7547335?v=4" alt="smarroufin" />
---
#default
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" alt="Avatar"}
:u-avatar{src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Avatar"}
:u-avatar{src="https://avatars.githubusercontent.com/u/7547335?v=4" alt="Avatar"}
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,59 @@
---
github: true
---
## Usage
Use the default slot to set the text of the Badge.
::component-card
---
code: Badge
---
Badge
::
You can also use the `label` prop:
::component-card
---
props:
label: Badge
---
::
### Style
Use the `color` and `variant` props to change the visual style of the Badge.
::component-card
---
props:
color: 'primary'
variant: 'solid'
---
Badge
::
### Size
Use the `size` prop to change the size of the Badge.
::component-card
---
props:
size: 'md'
---
Badge
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,280 @@
---
github: true
---
## Usage
Use the default slot to set the text of the Button.
::component-card
---
code: Button
---
Button
::
You can also use the `label` prop.
::component-card
---
props:
label: Button
---
::
### Style
Use the `color` and `variant` props to change the visual style of the Button.
::component-card
---
props:
color: 'primary'
variant: 'solid'
---
Button
::
Besides all the colors from the `ui.colors` object, you can also use the `white` and `gray` and `black` colors with their pre-defined variants.
#### White
::component-card
---
backgroundClass: 'bg-gray-50 dark:bg-gray-800'
props:
color: 'white'
variant: 'solid'
ui:
variant:
solid: 1
ghost: 1
excludedProps:
- color
---
Button
::
#### Gray
::component-card
---
props:
color: 'gray'
variant: 'solid'
ui:
variant:
solid: 1
ghost: 1
link: 1
excludedProps:
- color
---
Button
::
#### Black
::component-card
---
props:
color: 'black'
variant: 'solid'
ui:
variant:
solid: 1
link: 1
excludedProps:
- color
---
Button
::
### Size
Use the `size` prop to change the size of the Button.
::component-card
---
props:
size: 'sm'
---
Button
::
### 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.
::component-card
---
props:
icon: 'i-heroicons-pencil-square'
size: 'sm'
color: 'primary'
variant: 'solid'
label: Button
trailing: false
excludedProps:
- icon
---
::
The `label` as prop or slot is optional so you can use the Button as an icon-only button.
::component-card
---
props:
icon: 'i-heroicons-pencil-square'
size: 'sm'
color: 'primary'
square: true
variant: 'solid'
excludedProps:
- icon
- square
---
::
### Disabled
Use the `disabled` prop to disable the Button.
::component-card
---
props:
disabled: true
---
Button
::
### Loading
Use the `loading` prop to show a loading icon and disable the Button.
::component-card
---
props:
loading: true
---
Button
::
### Block
Use the `block` prop to make the Button fill the width of its container.
::component-card
---
props:
block: true
---
Button
::
### Link
Use the `to` prop to make the Button a link.
::component-card
---
props:
to: 'https://volta.net'
target: '_blank'
---
Button
::
### Padded
Use the `padded` prop to remove the padding of the Button.
::component-card
---
props:
padded: false
baseProps:
color: 'gray'
variant: 'link'
icon: 'i-heroicons-x-mark-20-solid'
---
::
### Square
Use the `square` prop to force the Button to have the same padding horizontally and vertically.
::component-card
---
props:
square: true
baseProps:
label: 'Button'
color: 'gray'
variant: 'solid'
---
::
### Truncate
Use the `truncate` prop to truncate the label of the Button.
::component-card
---
props:
truncate: true
class: 'w-20'
label: 'Button with a long text'
excludedProps:
- class
---
::
### Group
To stack buttons as a group, use the `ButtonGroup` component.
- To size all the buttons equally, pass the `size` prop
- To adjust the rounded or the shadow around buttons, customize with `ui.buttonGroup.rounded` or `ui.buttonGroup.shadow`
::component-card{slug="ButtonGroup"}
---
props:
size: 'sm'
ui:
size:
xxs: ''
xs: ''
sm: ''
md: ''
lg: ''
xl: ''
code: |
<UButton label="Action" color="white" />
<UButton icon="i-heroicons-chevron-down-20-solid" color="gray" />
---
#default
:u-button{label="Action" color="white"}
:u-button{icon="i-heroicons-chevron-down-20-solid" color="gray"}
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,56 @@
---
github: true
headlessui:
label: 'Menu'
to: 'https://headlessui.com/vue/menu'
---
## Usage
::component-example
#default
:dropdown-example
#code
```vue
<script setup>
const items = [
[{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
}], [{
label: 'Edit',
icon: 'i-heroicons-pencil-square-20-solid'
}, {
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'
}]
]
</script>
<template>
<UDropdown :items="items" :popper="{ placement: 'bottom-start' }">
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
</UDropdown>
</template>
```
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,91 @@
---
github: true
---
## Usage
::component-card
---
baseProps:
name: 'input'
---
::
### Size
Use the `size` prop to change the size of the Input.
::component-card
---
baseProps:
name: 'input'
props:
size: 'sm'
---
::
### 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.
::component-card
---
baseProps:
name: 'input'
props:
icon: 'i-heroicons-magnifying-glass-20-solid'
appearance: 'white'
size: 'sm'
trailing: false
placeholder: 'Search...'
excludedProps:
- icon
- placeholder
---
::
### Disabled
Use the `disabled` prop to disable the Input.
::component-card
---
baseProps:
name: 'input'
props:
placeholder: 'Search...'
appearance: 'white'
disabled: true
excludedProps:
- placeholder
---
::
### Loading
Use the `loading` prop to show a loading icon and disable the Input.
::component-card
---
baseProps:
name: 'input'
placeholder: 'Search'
props:
loading: true
icon: 'i-heroicons-magnifying-glass-20-solid'
excludedProps:
- icon
---
::
### Group
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,20 @@
---
github: true
---
## Usage
::component-card
---
baseProps:
name: 'textarea'
---
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,25 @@
---
github: true
---
## Usage
::component-card
---
baseProps:
name: 'select'
modelValue: 'Canada'
options:
- 'United States'
- 'Canada'
- 'Mexico'
---
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,185 @@
---
github: true
headlessui:
label: 'Listbox'
to: 'https://headlessui.com/vue/listbox'
---
## Usage
::component-example
#default
:select-menu-example-basic{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()
</script>
<template>
<USelectMenu v-model="selected" :options="people" />
</template>
```
::
You can use multiple values but you have to override the `#label` slot and handle the display yourself.
::component-example
#default
:select-menu-example-multiple{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([])
</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>
</template>
```
::
You can also override the default slot entirely.
::component-example
#default
:select-menu-example-button{class="max-w-[12rem] w-full"}
#code
```vue
<script setup>
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref(people[3])
</script>
<template>
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
<UButton>
{{ selected }}
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
</UButton>
</USelectMenu>
</template>
```
::
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.
::component-example
#default
:select-menu-example-objects{class="max-w-[12rem] w-full"}
#code
```vue
<script setup>
const people = [{
id: 'benjamincanac',
label: 'benjamincanac',
href: 'https://github.com/benjamincanac',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
},
{
id: 'Atinux',
label: 'Atinux',
href: 'https://github.com/Atinux',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
},
{
id: 'smarroufin',
label: 'smarroufin',
href: 'https://github.com/smarroufin',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
},
{
id: 'nobody',
label: 'Nobody',
icon: 'i-heroicons-user-circle'
}]
const selected = ref(people[0])
</script>
<template>
<USelectMenu v-model="selected" :options="people">
<template #label>
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4" />
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" />
{{ selected.label }}
</template>
</USelectMenu>
</template>
```
::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
::component-card
---
baseProps:
class: 'max-w-[12rem] w-full'
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'
excludedProps:
- icon
---
::
### Search
Use the `searchable` prop to enable search.
This will use HeadlessUI [Combobox](https://headlessui.com/vue/combobox) component instead of [Listbox](https://headlessui.com/vue/listbox).
::component-card
---
baseProps:
class: 'max-w-[12rem] w-full'
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:
searchable: true
---
::
### Disabled
Use the `disabled` prop to disable the SelectMenu.
::component-card
---
baseProps:
class: 'max-w-[12rem] w-full'
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:
disabled: true
---
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,16 @@
---
github: true
---
## Usage
::component-card
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,16 @@
---
github: true
---
## Usage
::component-card
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,19 @@
---
github: true
headlessui:
label: 'Switch'
to: 'https://headlessui.com/vue/switch'
---
## Usage
::component-card
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,46 @@
---
github: true
---
## Usage
::component-example
#default
:vertical-navigation-example
#code
```vue
<script setup>
const links = [{
label: 'Profile',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
}, {
label: 'Installation',
icon: 'i-heroicons-home',
to: '/getting-started/installation'
}, {
label: 'Vertical Navigation',
icon: 'i-heroicons-chart-bar',
to: '/navigation/vertical-navigation'
}, {
label: 'Command Palette',
icon: 'i-heroicons-command-line',
to: '/navigation/command-palette'
}]
</script>
<template>
<UVerticalNavigation :links="links" />
</template>
```
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,273 @@
---
github: true
headlessui:
label: 'Combobox'
to: 'https://headlessui.com/vue/combobox'
---
## Usage
Use a `v-model` to display a searchable and selectable list of commands.
::component-example
---
padding: false
---
#default
:command-palette-example-basic{class="h-[257px]"}
#code
```vue
<script setup>
const people = [
{ id: 1, label: 'Wade Cooper' },
{ id: 2, label: 'Arlene Mccoy' },
{ id: 3, label: 'Devon Webb' },
{ id: 4, label: 'Tom Cook' },
{ id: 5, label: 'Tanya Fox' },
{ id: 6, label: 'Hellen Schmidt' },
{ id: 7, label: 'Caroline Schultz' },
{ id: 8, label: 'Mason Heaney' },
{ id: 9, label: 'Claudie Smitham' },
{ id: 10, label: 'Emil Schaefer' }
]
const selected = ref([people[3]])
</script>
<template>
<UCommandPalette
v-model="selected"
multiple
nullable
:groups="[{ key: 'people', commands: people }]"
:fuse="{ resultLimit: 6 }"
/>
</template>
```
::
You can put a `CommandPalette` anywhere you want but it's most commonly used inside of a modal.
::component-example
#default
:command-palette-example-modal
#code
```vue
<script setup>
const open = ref(false)
const people = [
{ id: 1, label: 'Wade Cooper' },
{ id: 2, label: 'Arlene Mccoy' },
{ id: 3, label: 'Devon Webb' },
{ id: 4, label: 'Tom Cook' },
{ id: 5, label: 'Tanya Fox' },
{ id: 6, label: 'Hellen Schmidt' },
{ id: 7, label: 'Caroline Schultz' },
{ id: 8, label: 'Mason Heaney' },
{ id: 9, label: 'Claudie Smitham' },
{ id: 10, label: 'Emil Schaefer' }
]
const selected = ref([])
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<UModal v-model="open">
<UCommandPalette
v-model="selected"
multiple
nullable
:groups="[{ key: 'people', commands: people }]"
/>
</UModal>
</div>
</template>
```
::
You can pass multiple groups of commands to the component. Each group will be separated by a divider and will display a label.
Without a `v-model`, you can also listen on `@update:model-value` to navigate to a link or do something else when a command is clicked.
::component-example
---
padding: false
---
#default
:command-palette-example-groups{class="h-[274px]"}
#code
```vue
<script setup>
const router = useRouter()
const commandPaletteRef = ref()
const users = [
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } },
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' } },
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' } }
]
const actions = [
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file') },
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder') },
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag') },
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label') }
]
const groups = computed(() => commandPaletteRef.value?.query
? [{
key: 'users',
commands: users
}]
: [{
key: 'recent',
label: 'Recent searches',
commands: users.slice(0, 1)
}, {
key: 'actions',
commands: actions
}])
function onSelect (option) {
if (option.click) {
option.click()
} else if (option.to) {
router.push(option.to)
} else if (option.href) {
window.open(option.href, '_blank')
}
}
</script>
<template>
<UCommandPalette ref="commandPaletteRef" :groups="groups" @update:model-value="onSelect" />
</template>
```
::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
::component-card
---
padding: false
baseProps:
empty: null
props:
icon: 'i-heroicons-command-line'
excludedProps:
- icon
---
::
### Placeholder
Use the `placeholder` prop to change the input placeholder
::component-card
---
padding: false
baseProps:
empty: null
props:
placeholder: 'Type a command...'
excludedProps:
- icon
---
::
### Close
Use the `close` 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`.
::component-card
---
padding: false
baseProps:
empty: null
props:
close:
icon: 'i-heroicons-x-mark-20-solid'
color: 'gray'
variant: 'link'
padded: false
excludedProps:
- close
---
::
### Empty
Use the `empty` prop to display a message 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:
::component-card
---
padding: false
baseProps:
placeholder: 'Type something to see the empty label change'
props:
empty:
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
---
::
## Themes
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
### 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/docs/rework/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/docs/rework/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
Take a look at the component!
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,72 @@
---
github: true
headlessui:
label: 'Dialog'
to: 'https://headlessui.com/vue/dialog'
---
## Usage
::component-example
#default
:modal-example-basic
#code
```vue
<script setup>
const open = ref(false)
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<UModal v-model="open">
<!-- Content -->
</UModal>
</div>
</template>
```
::
You can put a [Card](/layout/card) component inside your Modal to handle forms and take advantage of `header` and `footer` slots:
::component-example
#default
:modal-example-card
#code
```vue
<script setup>
const open = ref(false)
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<UModal v-model="open">
<UCard :ui="{ divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<!-- Content -->
</template>
<!-- Content -->
<template #footer>
<!-- Content -->
</template>
</UCard>
</UModal>
</div>
</template>
```
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,37 @@
---
github: true
headlessui:
label: 'Dialog'
to: 'https://headlessui.com/vue/dialog'
---
## Usage
::component-example
#default
:slideover-example
#code
```vue
<script setup>
const open = ref(false)
</script>
<template>
<div>
<UButton label="Open" @click="open = true" />
<USlideover v-model="open">
<!-- Content -->
</USlideover>
</div>
</template>
```
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,33 @@
---
github: true
headlessui:
label: 'Popover'
to: 'https://headlessui.com/vue/popover'
---
## Usage
::component-example
#default
:popover-example
#code
```vue
<template>
<UPopover>
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
<template #panel>
<!-- Content -->
</template>
</UPopover>
</template>
```
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,26 @@
---
github: true
---
## Usage
::component-example
#default
:tooltip-example
#code
```vue
<template>
<UTooltip text="Tooltip">
<UButton color="gray" label="Button" />
</UTooltip>
</template>
```
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,49 @@
---
github: true
---
## Usage
::component-example
#default
:context-menu-example
#code
```vue
<script setup>
const { x, y } = useMouse()
const isOpen = ref(false)
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
function openContextMenu () {
const top = unref(y)
const left = unref(x)
virtualElement.value.getBoundingClientRect = () => ({
width: 0,
height: 0,
top,
left
})
isOpen.value = true
}
</script>
<template>
<div @contextmenu.prevent="openContextMenu">
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" width-class="w-48">
<!-- Content -->
</UContextMenu>
</div>
</template>
```
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,249 @@
---
github: true
---
## Usage
First of all, add the `Notifications` component to your app, preferably inside `app.vue`.
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`.
```vue [app.vue]
<template>
<div>
<UContainer>
<NuxtPage />
</UContainer>
<UNotifications />
</div>
</template>
```
Then, you can use the `useToast` composable to add notifications to your app:
::component-example
#default
:notification-example-basic
#code
```vue
<script setup>
const toast = useToast()
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'Hello world!' })" />
</template>
```
::
### Description
You can add a `description` in addition of the `title`.
::component-card
---
baseProps:
id: 2
timeout: 0
props:
title: 'Notification'
description: 'This is a notification.'
---
::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
::component-card
---
baseProps:
id: 3
timeout: 0
title: 'Notification'
props:
icon: 'i-heroicons-check-circle'
description: 'This is a notification.'
excludedProps:
- icon
---
::
### Avatar
Use the [avatar](/elements/avatar) prop as an `object` and configure it with any of its props.
::component-card
---
baseProps:
id: 4
timeout: 0
title: 'Notification'
description: 'This is a notification.'
props:
avatar:
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
excludedProps:
- avatar
---
::
### Timeout
Use the `timeout` prop to configure how long the Notification will remain. Set it to `0` to disable the timeout.
You will see a progress bar at the bottom of the Notification which will indicate the remaining time. When hovering the Notification, the progress bar will be paused.
::component-card
---
baseProps:
id: 5
title: 'Notification'
description: 'This is a notification.'
props:
timeout: 10000
---
::
### Click
Use the `click` prop to execute a function when the Notification is clicked.
::component-example
#default
:notification-example-click
#code
```vue
<script setup>
const toast = useToast()
function onClick () {
alert('Clicked!')
}
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: onClick })" />
</template>
```
::
### Callback
Use the `callback` prop to execute a function when the Notification expires.
::component-example
#default
:notification-example-callback
#code
```vue
<script setup>
const toast = useToast()
function onCallback () {
alert('Notification expired!')
}
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'Expires soon...', timeout: 1000, callback: onCallback })" />
</template>
```
::
### Close
Use the `close` 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`.
::component-card
---
baseProps:
id: 6
title: 'Notification'
timeout: 0
props:
close:
icon: 'i-heroicons-archive-box-x-mark'
color: 'primary'
variant: 'outline'
padded: true
size: '2xs'
ui:
rounded: 'rounded-full'
excludedProps:
- close
---
::
### Actions
Use the `actions` prop to add actions to the Notification.
::component-example
#default
:notification-example-actions
#code
```vue
<script setup>
const toast = useToast()
</script>
<template>
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: () => alert('Clicked!') })" />
</template>
```
::
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`.
::component-card
---
baseProps:
id: 6
title: 'Notification'
timeout: 0
props:
actions:
- variant: 'ghost'
color: 'gray'
label: Action 1
- variant: 'solid'
color: 'gray'
label: Action 2
excludedProps:
- actions
---
::
Actions will render differently whether you have a `description` set.
::component-card
---
baseProps:
id: 6
title: 'Notification'
description: 'This is a notification.'
timeout: 0
props:
actions:
- variant: 'solid'
color: 'primary'
label: Action 1
- variant: 'outline'
color: 'primary'
label: Action 2
excludedProps:
- actions
---
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -0,0 +1,35 @@
---
github: true
---
## Usage
::component-example
#default
:card-example{class="w-full"}
#code
```vue
<template>
<UCard>
<template #header />
Body
<template #footer />
</UCard>
</template>
```
::
## Props
:component-props
## Slots
:component-slots
## Preset
:component-preset

View File

@@ -0,0 +1,25 @@
---
github: true
---
## Usage
::component-example
#default
:container-example{class="w-full"}
#code
```vue
<template>
<UContainer>Content</UContainer>
</template>
```
::
## Props
:component-props
## Preset
:component-preset

19
docs/layouts/default.vue Normal file
View File

@@ -0,0 +1,19 @@
<template>
<div class="relative grid lg:grid-cols-10 lg:gap-8">
<DocsAside class="lg:col-span-2" />
<div class="relative lg:col-span-6 pt-8 pb-16">
<DocsPageHeader />
<div class="prose prose-primary dark:prose-invert max-w-none">
<slot />
</div>
<hr class="border-gray-200 dark:border-gray-800 my-12">
<DocsPrevNext />
</div>
<DocsToc class="lg:col-span-2 order-first lg:order-last" />
</div>
</template>

View File

@@ -1,46 +1,38 @@
/* @unocss-include */
import ui from '../src/module'
import { defineNuxtConfig } from 'nuxt3'
import module from '../src/module'
// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
meta: {
title: '@nuxthq/ui',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
],
link: [
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }
],
htmlAttrs: {
class: 'u-bg-white'
},
bodyAttrs: {
class: 'u-bg-gray-50 u-text-gray-700'
// @ts-ignore
modules: [
ui,
'@vueuse/nuxt',
'@nuxt/content',
'@nuxtjs/plausible',
'nuxt-lodash',
'nuxt-component-meta'
],
content: {
documentDriven: true,
highlight: {
theme: {
light: 'material-lighter',
dark: 'material-palenight'
},
preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'yaml', 'bash', 'ini']
}
},
buildModules: [
module
],
ui: {
colors: {
primary: 'blue'
},
preset: {
container: {
constrained: 'max-w-8xl'
}
},
unocss: {
theme: {
fontFamily: {
sans: '"Inter var", sans-serif'
},
maxWidth: {
'8xl': '90rem'
}
}
global: true,
icons: ['heroicons', 'simple-icons']
},
typescript: {
strict: false,
includeWorkspace: true
},
// @ts-ignore
$production: {
routeRules: {
'/api/_content/**': { isr: true, static: true },
'/api/component-meta/**': { isr: true, static: true }
}
}
})

View File

@@ -1,372 +0,0 @@
<template>
<UCard v-if="component" class="relative flex flex-col lg:h-[calc(100vh-10rem)]" body-class="px-4 py-5 sm:p-6 relative" footer-class="flex flex-col flex-1 overflow-hidden">
<div class="flex justify-center">
<component :is="`U${defaultProps[params.component].component.name}`" v-if="defaultProps[params.component] && defaultProps[params.component].component" v-bind="defaultProps[params.component].component.props" />
<component :is="is" v-bind="{ ...boundProps, ...eventProps }">
<template v-for="[key, slot] of Object.entries(defaultProps[params.component]?.slots || {}) || []" #[key]>
<template v-if="Array.isArray(slot)">
<div :key="key">
<component
:is="slot.component ? `U${slot.component}` : slot.tag"
v-for="(slot, index) of slot"
:key="index"
:class="slot.class"
v-bind="slot.props || defaultProps[slot.component]"
v-html="slot.html"
/>
</div>
</template>
<template v-else>
<component :is="slot.component ? `U${slot.component}` : slot.tag" :key="key" :class="slot.class" v-bind="slot.props || defaultProps[slot.component]" v-html="slot.html" />
</template>
</template>
</component>
</div>
<template v-if="props.length" #footer>
<div class="flex-1 px-4 py-5 sm:p-6 space-y-3 lg:overflow-y-auto">
<UFormGroup
v-for="prop of props"
:key="prop.key"
class="capitalize"
:name="prop.key"
:label="prop.key"
>
<UToggle
v-if="prop.type === 'Boolean'"
v-model="prop.value"
:name="prop.key"
:label="prop.key"
/>
<USelect
v-else-if="prop.values"
v-model="prop.value"
:name="prop.key"
placeholder="Choose one..."
:options="prop.values"
size="sm"
/>
<UInput
v-else-if="prop.type === 'String'"
v-model="prop.value"
:name="prop.key"
size="sm"
autocomplete="off"
/>
<UInput
v-else-if="prop.type === 'Number'"
v-model="prop.value"
type="number"
:name="prop.key"
size="sm"
/>
<UTextarea
v-else
v-model="prop.value"
:name="prop.key"
size="sm"
:rows="8"
autoresize
/>
</UFormGroup>
</div>
<div class="border-t u-border-gray-200 u-bg-gray-50">
<pre class="text-sm leading-6 u-text-gray-900 flex-1 relative flex ligatures-none lg:overflow-y-auto overflow-x-hidden px-4 sm:px-6 py-5 sm:py-6">
<code class="flex-none min-w-full whitespace-pre-wrap break-all">{{ code }}</code>
<UButton
class="absolute top-0 right-0"
:icon="copied ? 'heroicons-outline:clipboard-check' : 'heroicons-outline:clipboard-copy'"
variant="transparent"
size="sm"
:custom-class="copied ? '!text-green-500' : ''"
@click="onCopy"
/>
</pre>
</div>
</template>
</UCard>
</template>
<script setup>
import $ui from '#build/ui'
const nuxtApp = useNuxtApp()
const { params } = useRoute()
const is = `U${params.component}`
const component = nuxtApp.vueApp.component(is)
const alertDialog = ref(false)
const toggle = ref(false)
const modal = ref(false)
const defaultProps = {
Button: {
label: 'Button text'
},
Badge: {
label: 'Badge'
},
Alert: {
title: 'A new software update is available. See whats new in version 2.0.4.'
},
AlertDialog: {
title: 'Are you sure you want to close this modal?',
modelValue: alertDialog,
'onUpdate:modelValue': (v) => { alertDialog.value = v },
component: {
name: 'Button',
props: {
variant: 'secondary',
label: 'Open modal',
onClick: () => { alertDialog.value = true }
}
}
},
Avatar: {
src: 'https://picsum.photos/200/300'
},
AvatarGroup: {
group: ['https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', 'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80', 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80']
},
Dropdown: {
items: [
[{
label: 'Edit',
icon: 'heroicons-solid:pencil'
}, {
label: 'Duplicate',
icon: 'heroicons-solid:duplicate'
}],
[{
label: 'Archive',
icon: 'heroicons-solid:archive'
}, {
label: 'Move',
icon: 'heroicons-solid:external-link'
}],
[{
label: 'Delete',
icon: 'heroicons-solid:trash'
}]
]
},
VerticalNavigation: {
links: [
{
label: 'Home',
icon: 'heroicons-outline:home',
to: '/'
},
{
label: 'Examples',
icon: 'heroicons-outline:book-open',
to: '/examples'
},
{
label: 'Migration',
icon: 'heroicons-outline:refresh',
to: '/migration'
},
{
label: 'External link',
icon: 'heroicons-outline:external-link',
to: 'https://google.fr',
target: '_blank'
}
]
},
Icon: {
name: 'heroicons-outline:bell'
},
Input: {
name: 'input',
placeholder: 'Enter text'
},
FormGroup: {
name: 'input',
label: 'Input group',
slots: {
default: {
component: 'Input',
props: {
name: 'input',
placeholder: 'Works with every form element'
}
}
}
},
Toggle: {
modelValue: toggle,
'onUpdate:modelValue': (v) => { toggle.value = v }
},
Checkbox: {
name: 'checkbox'
},
Radio: {
name: 'radio'
},
Select: {
name: 'select',
options: ['English', 'Spanish', 'French', 'German', 'Chinese']
},
Textarea: {
name: 'textarea'
},
Tooltip: {
text: 'Tooltip text'
},
Notification: {
id: '1',
title: 'Notification title',
callback: 'console.log(\'Timer expired\')'
},
Modal: {
modelValue: modal,
'onUpdate:modelValue': (v) => { modal.value = v },
component: {
name: 'Button',
props: {
variant: 'secondary',
label: 'Open modal',
onClick: () => { modal.value = true }
}
},
slots: {
default: {
tag: 'div',
html: 'Modal content'
},
footer: {
component: 'Button',
props: {
label: 'Close',
onClick: () => { modal.value = false }
}
}
}
},
Popover: {
slots: {
panel: {
tag: 'div',
class: 'u-bg-gray-100 rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 p-6',
html: 'Popover content'
}
}
}
}
const componentDefaultProps = defaultProps[params.component] || {}
const { props: componentProps } = await component.__asyncLoader()
const refProps = Object.entries(componentProps).map(([key, prop]) => {
const defaultValue = componentDefaultProps[key]
const propDefault = (typeof prop.default === 'function' ? prop.default() : prop.default)
let value = defaultValue !== undefined ? defaultValue : propDefault
let type = prop.type
if (Array.isArray(type)) {
type = type[0].name
} else {
type = type.name
}
let values
if (prop.validator) {
const arrayRegex = prop.validator.toString().match(/\[.*\]/g, '')
if (arrayRegex) {
values = JSON.parse(arrayRegex[0].replace(/'/g, '"')).filter(Boolean)
} else {
const $uiProp = $ui[params.component.toLowerCase()][key]
if ($uiProp) {
values = Object.keys($uiProp).filter(Boolean)
}
}
}
if (value) {
if (type === 'String') {
value = value.replace(/^'(.*)'$/, '$1')
} else if (type === 'Array') {
value = JSON.stringify(value)
}
}
return {
key,
type,
value,
values,
default: propDefault
}
})
const eventProps = Object.entries(componentDefaultProps)
.filter(([key]) => !refProps.find(prop => prop.key === key))
.reduce((acc, [key, value]) => {
acc[key] = value
return acc
}, {})
const props = ref(refProps)
const boundProps = computed(() => {
const bound = {}
for (const prop of props.value) {
let value = prop.value
if (value === null) {
continue
}
try {
if (prop.type === 'Array') {
value = JSON.parse(value)
} else if (prop.type === 'Number') {
value = Number(value)
} else if (prop.type === 'Function') {
// eslint-disable-next-line no-new-func
value = Function(value)
}
bound[prop.key] = value
} catch (e) {
continue
}
}
return bound
})
function toKebabCase (str) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}
const copied = ref(false)
const onCopy = () => {
navigator.clipboard.writeText(code.value).then(() => {
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
})
}
const code = computed(() => {
let code = `<U${params.component}`
for (const prop of props.value) {
if (prop.value === null) {
continue
}
if (prop.value === prop.default) {
continue
}
const key = toKebabCase(prop.key)
code += `\n ${prop.type === 'Boolean' ? ':' : ''}${key === 'model-value' ? 'v-model' : key}="${prop.value}"`
}
code += '\n/>'
return code
})
</script>

View File

@@ -1,81 +0,0 @@
<template>
<div class="space-y-4">
<div class="pb-10 border-b u-border-gray-200 mb-10">
<div>
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
Dark mode
</h1>
</div>
<p class="mt-1 text-lg u-text-gray-500">
Dark mode implementation with <a href="https://vueuse.org" target="_blank" class="underline">VueUse</a>.
</p>
</div>
<h2 class="font-bold text-2xl u-text-gray-900">
Usage
</h2>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm">
{{ code5 }}
</code>
</pre>
<p>The VueUse <a href="https://vueuse.org/core/useDark" target="_blank" class="underline">useDark</a> composable makes use of the `dark` class on the html tag so you can easily take advantage of the UnoCSS `dark` variant.</p>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm">
{{ code4 }}
</code>
</pre>
<p>You can implement a toggle button easily by doing:</p>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm">
{{ code2 }}
</code>
</pre>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm">
{{ code1 }}
</code>
</pre>
<h2 class="font-bold text-2xl u-text-gray-900">
Shortcuts
</h2>
<p>A number of shortcuts are available to make your life with colors easier.</p>
<p>For each color utilities: `bg`, `text`, `border`, `ring` and `divide`, shortcuts for `white`, `black` and `gray` colors are generated (based on your prefix `u` by default) that handles the dark mode automatically:</p>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm">
{{ code3 }}
</code>
</pre>
<p>For example `u-bg-gray-100` is a shortcut for `bg-gray-100 dark:bg-gray-800`. Take a look at the <a href="https://github.com/nuxtlabs/ui/blob/dev/src/index.ts#L61" target="_blank" class="underline">shortcuts definitions</a>.</p>
</div>
</template>
<script setup>
const code1 = `
<UseDark v-slot="{ isDark, toggleDark }">
<UButton variant="transparent" :icon="isDark ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark()" />
</UseDark>`
const code2 = `
import { UseDark } from '@vueuse/components'
`
const code3 = `
<div class="u-bg-gray-100 border u-border-gray-200 u-text-gray-700"></div>
`
const code4 = `
<div class="bg-white dark:bg-black"></div>
`
const code5 = `
yarn add @vueuse/components @vueuse/core
`
</script>

View File

@@ -1,380 +0,0 @@
<template>
<div class="space-y-4">
<div class="pb-10 border-b u-border-gray-200 mb-10">
<div>
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
Examples
</h1>
</div>
<p class="mt-1 text-lg u-text-gray-500">
Examples of real-life usage of components.
</p>
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Avatar:
</div>
<UAvatar src="https://picsum.photos/200/300" />
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Button:
</div>
<UButton variant="primary" icon="heroicons-outline:mail">
Button text
</UButton>
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Modal:
</div>
<UButton @click="toggleModalIsOpen()">
Toggle modal!
</UButton>
<UModal v-model="isModalOpen">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<UIcon name="heroicons-outline:exclamation" class="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium u-text-gray-900">
Deactivate account
</h3>
<div class="mt-2">
<p class="text-sm u-text-gray-500">
Are you sure you want to deactivate your account? All of your data will be permanently removed from our servers forever. This action cannot be undone.
</p>
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm" @click="open = false">
Deactivate
</button>
<button ref="cancelButtonRef" type="button" class="mt-3 w-full inline-flex justify-center rounded-md border u-border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium u-text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm" @click="open = false">
Cancel
</button>
</div>
</UModal>
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Dropdown:
</div>
<UDropdown v-slot="{ open }" :items="dropdownItems" placement="bottom-start">
<UButton variant="white" :icon="open ? 'heroicons-solid:chevron-up' : 'heroicons-solid:chevron-down'" trailing icon-class="transition">
Open menu!
</UButton>
</UDropdown>
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Dropdown with avatar:
</div>
<UDropdown :items="customDropdownItems" placement="bottom-end">
<button class="flex">
<UAvatar src="https://picsum.photos/200/300" />
</button>
<template #item-with-avatar="{ item }">
<UAvatar v-if="item.avatar" :src="item.avatar" size="xxs" class="mr-3" />
{{ item.label }}
</template>
</UDropdown>
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Popover:
</div>
<UPopover panel-class="w-screen max-w-sm px-4 sm:px-0 transform" wrapper-class="inline-block relative">
<template #default="{ open }">
<UButton variant="secondary" :icon="open ? 'heroicons-solid:chevron-up' : 'heroicons-solid:chevron-down'" trailing icon-class="transition">
Open popover!
</UButton>
</template>
<template #panel>
<div
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
>
<div class="relative grid gap-8 bg-white p-7">
<a
v-for="item in solutions"
:key="item.name"
:href="item.href"
class="flex items-center p-2 -m-3 transition duration-150 ease-in-out rounded-lg hover:bg-gray-50 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
>
<div
class="flex items-center justify-center flex-shrink-0 w-10 h-10 text-white sm:h-12 sm:w-12"
>
<div v-html="item.icon" />
</div>
<div class="ml-4">
<p class="text-sm font-medium u-text-gray-900">
{{ item.name }}
</p>
<p class="text-sm u-text-gray-500">
{{ item.description }}
</p>
</div>
</a>
</div>
<div class="p-4 bg-gray-50">
<a
href="##"
class="flow-root px-2 py-2 transition duration-150 ease-in-out rounded-md hover:bg-gray-100 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
>
<span class="flex items-center">
<span class="text-sm font-medium u-text-gray-900">
Documentation
</span>
</span>
<span class="block text-sm u-text-gray-500">
Start integrating products and tools
</span>
</a>
</div>
</div>
</template>
</UPopover>
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Tooltip:
</div>
<UTooltip text="Hello tooltip!">
<UIcon name="heroicons-outline:information-circle" class="w-6 h-6 text-black cursor-pointer" />
</UTooltip>
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Notifications:
</div>
<UButton icon="heroicons-outline:bell" variant="red" label="Trigger an error" @click="onNotificationClick" />
</div>
<div>
<div class="font-medium text-sm mb-1 u-text-gray-700">
Card:
</div>
<UCard body-class="flex" @submit.prevent="onSubmit">
<div class="flex-1 px-4 py-5 sm:p-6 space-y-3">
<UFormGroup label="Email" name="email" required>
<UInput v-model="form.email" type="email" name="email" required />
</UFormGroup>
<UFormGroup label="Description" name="description">
<UTextarea v-model="form.description" type="description" name="description" autoresize />
</UFormGroup>
<UFormGroup label="Toggle" name="toggle">
<UToggle v-model="form.toggle" name="toggle" icon-off="heroicons-solid:x" icon-on="heroicons-solid:check" />
</UFormGroup>
<UFormGroup label="Notifications" label-class="text-base font-medium u-text-gray-900" description="How do you prefer to receive notifications?">
<div class="space-y-4 mt-3">
<URadio v-model="form.notification" value="email" label="Email" help="Email" />
<URadio v-model="form.notification" value="phone" label="Phone (SMS)" help="Phone (SMS)" />
<URadio v-model="form.notification" value="push" label="Push notification" help="Push notification" />
</div>
</UFormGroup>
<UCard body-class="px-4 py-5 space-y-5">
<UCheckbox v-model="form.notifications" name="comments" value="comments" label="Comments" help="Get notified when someones posts a comment on a posting." />
<UCheckbox v-model="form.notifications" name="candidates" value="candidates" label="Candidates" help="Get notified when a candidate applies for a job." />
<UCheckbox v-model="form.notifications" name="offers" value="offers" label="Offers" help="Get notified when a candidate accepts or rejects an offer." />
</UCard>
<div>
<UCheckbox v-model="form.terms" label="I agree to the terms and conditions" name="terms" />
</div>
<div class="flex justify-end">
<UButton type="submit" label="Submit" class="ml-auto" />
</div>
</div>
<div class="w-1/3 px-4 py-5 sm:p-6 border-l u-border-gray-200 u-bg-gray-50">
<pre class="whitespace-pre-wrap break-all">
{{ form }}
</pre>
</div>
</UCard>
</div>
</div>
</template>
<script setup>
const isModalOpen = ref(false)
const form = reactive({
email: '',
description: '',
toggle: false,
notification: 'email',
notifications: [],
terms: false
})
const { $toast } = useNuxtApp()
function toggleModalIsOpen () {
isModalOpen.value = !isModalOpen.value
}
function onClick () {
// eslint-disable-next-line no-console
console.warn('click')
}
function onSubmit () {
// eslint-disable-next-line no-console
console.warn('submit')
}
const dropdownItems = [
[{
label: 'Edit',
icon: 'heroicons-solid:pencil',
click: () => onClick()
}, {
label: 'Duplicate',
icon: 'heroicons-solid:duplicate'
}],
[{
label: 'Archive',
icon: 'heroicons-solid:archive'
}, {
label: 'Move',
icon: 'heroicons-solid:external-link'
}],
[{
label: 'Delete',
icon: 'heroicons-solid:trash'
}]
]
const customDropdownItems = [
[{
label: 'benjamincanac',
avatar: 'https://picsum.photos/200/300',
href: 'https://google.fr',
target: '_blank',
slot: 'item-with-avatar'
}],
[{
label: 'About',
icon: 'heroicons-solid:plus',
to: '/about'
}]
]
const solutions = [
{
name: 'Insights',
description: 'Measure actions your users take',
href: '##',
icon: `
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
aria-hidden='true'
xmlns="http://www.w3.org/2000/svg"
>
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
<path
d="M24 11L35.2583 17.5V30.5L24 37L12.7417 30.5V17.5L24 11Z"
stroke="#FB923C"
stroke-width="2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.7417 19.8094V28.1906L24 32.3812L31.2584 28.1906V19.8094L24 15.6188L16.7417 19.8094Z"
stroke="#FDBA74"
stroke-width="2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M20.7417 22.1196V25.882L24 27.7632L27.2584 25.882V22.1196L24 20.2384L20.7417 22.1196Z"
stroke="#FDBA74"
stroke-width="2"
/>
</svg>
`
},
{
name: 'Automations',
description: 'Create your own targeted content',
href: '##',
icon: `
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
<path
d="M28.0413 20L23.9998 13L19.9585 20M32.0828 27.0001L36.1242 34H28.0415M19.9585 34H11.8755L15.9171 27"
stroke="#FB923C"
stroke-width="2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.804 30H29.1963L24.0001 21L18.804 30Z"
stroke="#FDBA74"
stroke-width="2"
/>
</svg>
`
},
{
name: 'Reports',
description: 'Keep track of your growth',
href: '##',
icon: `
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
<rect x="13" y="32" width="2" height="4" fill="#FDBA74" />
<rect x="17" y="28" width="2" height="8" fill="#FDBA74" />
<rect x="21" y="24" width="2" height="12" fill="#FDBA74" />
<rect x="25" y="20" width="2" height="16" fill="#FDBA74" />
<rect x="29" y="16" width="2" height="20" fill="#FB923C" />
<rect x="33" y="12" width="2" height="24" fill="#FB923C" />
</svg>
`
}
]
const onNotificationClick = () => {
$toast.error({ title: 'Error', description: 'This is an error message' })
}
</script>

View File

@@ -1,128 +1,7 @@
<template>
<div class="space-y-4">
<div class="pb-10 border-b u-border-gray-200 mb-10">
<div>
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
Documentation
</h1>
</div>
<p class="mt-1 text-lg u-text-gray-500">
Components library as a Nuxt3 module using <a href="https://github.com/antfu/unocss" target="_blank" class="underline">UnoCSS</a>.
</p>
</div>
<h2 class="font-bold text-2xl u-text-gray-900">
Installation
</h2>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm" v-html="code1" />
</pre>
<p>Then, register the module in your `nuxt.config.js`:</p>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm" v-html="code2" />
</pre>
<p>If you want latest updates, please use `@nuxthq/ui-edge` in your `package.json`:</p>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm" v-html="code3" />
</pre>
<h2 class="font-bold text-2xl u-text-gray-900">
Options
</h2>
<p>- `preset`</p>
<p>Choose preset. Defaults to `tailwindui`. An object can also be used to override some parts of the default preset.</p>
<p>- `prefix`</p>
<p>Define the prefix of the imported components. Defaults to `u`.</p>
<p class="font-medium">
Example:
</p>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm" v-html="code4" />
</pre>
<p>- `colors.primary`</p>
<p>Define the primary variant. Defaults to `indigo`. You can specify your own object of colors like here:</p>
<p class="font-medium">
Example:
</p>
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
<code class="text-sm" v-html="code5" />
</pre>
<p>- `colors.gray`</p>
<p>Define the gray variant. Defaults to `zinc`. You can like the `primary` color specify your own object. https://github.com/antfu/unocss/blob/main/packages/preset-uno/src/theme/colors.ts</p>
<p>- `unocss.shortcuts`. Defaults to `[]`.</p>
<p>Define UnoCSS shortcuts: https://github.com/antfu/unocss#shortcuts.</p>
<p>- `unocss.rules`. Defaults to `[]`.</p>
<p>Customize UnoCSS rules: https://github.com/antfu/unocss#custom-rules.</p>
<p>- `unocss.variants`. Defaults to `[]`.</p>
<p>Customize UnoCSS variants: https://github.com/antfu/unocss#custom-variants.</p>
<p>- `unocss.theme`. Defaults to `{}`.</p>
<p>Extend UnoCSS theme: https://github.com/antfu/unocss#extend-theme.</p>
</div>
<div />
</template>
<script setup>
const code1 = `
yarn add --dev @nuxthq/ui`
const code2 = `
import { defineNuxtConfig } from 'nuxt3'
defineNuxtConfig({
buildModules: [
'@nuxthq/ui'
]
})`
const code3 = `
{
"devDependencies": {
"@nuxthq/ui": "npm:@nuxthq/ui-edge@latest"
}
}`
const code4 = `
import { defineNuxtConfig } from 'nuxt3'
defineNuxtConfig({
ui: {
prefix: 'tw'
}
})`
const code5 = `
import { defineNuxtConfig } from 'nuxt3'
defineNuxtConfig({
ui: {
colors: {
primary: 'blue'
}
}
})`
<script setup lang="ts">
await navigateTo('/getting-started/installation')
</script>

View File

@@ -1,234 +0,0 @@
<template>
<div class="space-y-4">
<div class="pb-10 border-b u-border-gray-200 mb-10">
<div>
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
Migration
</h1>
</div>
<p class="mt-1 text-lg u-text-gray-500">
Check out the components that have been migrated to Vue3 coming from `@nuxthq/volta-ui`.
</p>
</div>
<UCard body-class>
<table class="min-w-full divide-y u-divide-gray-200">
<thead class="u-bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
Component
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
Nuxt3 ready
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
Composition API
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
Preset system
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
Typescript
</th>
</tr>
</thead>
<tbody>
<tr v-for="(component, index) of components" :key="index" :class="index % 2 === 0 ? 'u-bg-white' : 'u-bg-gray-50'">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium u-text-gray-900">
<NuxtLink :to="component.to" class="hover:underline">
{{ component.label }}
</NuxtLink>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
<span v-if="component.nuxt3 || component.capi"></span>
<span v-else></span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
<span v-if="component.capi"></span>
<span v-else></span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
<span v-if="component.preset"></span>
<span v-else></span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
<span v-if="component.typescript"></span>
<span v-else></span>
</td>
</tr>
</tbody>
</table>
</UCard>
</div>
</template>
<script setup>
const components = [
{
label: 'Avatar',
to: '/components/Avatar',
nuxt3: true
},
{
label: 'AvatarGroup',
to: '/components/AvatarGroup',
nuxt3: true
},
{
label: 'Badge',
to: '/components/Badge',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Button',
to: '/components/Button',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Dropdown',
to: '/components/Dropdown',
nuxt3: true,
capi: true
},
{
label: 'Icon',
to: '/components/Icon',
nuxt3: true,
capi: true
},
{
label: 'Link',
to: '/components/Link',
nuxt3: true,
capi: true
},
{
label: 'Toggle',
to: '/components/Toggle',
nuxt3: true,
preset: true,
capi: true
},
{
label: 'Alert',
to: '/components/Alert',
nuxt3: true
},
{
label: 'AlertDialog',
to: '/components/AlertDialog',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Input',
to: '/components/Input',
capi: true,
preset: true
},
{
label: 'FormGroup',
to: '/components/FormGroup',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Checkbox',
to: '/components/Checkbox',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Radio',
to: '/components/Radio',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Select',
to: '/components/Select',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'SelectCustom',
to: '/components/SelectCustom'
},
{
label: 'Textarea',
to: '/components/Textarea',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Card',
to: '/components/Card',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Container',
to: '/components/Container',
nuxt3: true,
preset: true,
capi: true
},
{
label: 'Pills',
to: '/components/Pills'
},
{
label: 'Tabs',
to: '/components/Tabs'
},
{
label: 'VerticalNavigation',
to: '/components/VerticalNavigation',
nuxt3: true,
capi: true,
preset: true
},
{
label: 'Modal',
to: '/components/Modal',
nuxt3: true,
capi: true
},
{
label: 'Notification',
to: '/components/Notification',
nuxt3: true,
capi: true
},
{
label: 'Notifications',
to: '/components/Notifications',
nuxt3: true,
capi: true
},
{
label: 'Popover',
to: '/components/Popover',
nuxt3: true,
capi: true
},
{
label: 'Tooltip',
to: '/components/Tooltip',
nuxt3: true,
capi: true
}
]
</script>

View File

@@ -0,0 +1,3 @@
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M504.908 750H839.476C850.103 750.001 860.542 747.229 869.745 741.963C878.948 736.696 886.589 729.121 891.9 719.999C897.211 710.876 900.005 700.529 900 689.997C899.995 679.465 897.193 669.12 891.873 660.002L667.187 274.289C661.876 265.169 654.237 257.595 645.036 252.329C635.835 247.064 625.398 244.291 614.773 244.291C604.149 244.291 593.711 247.064 584.511 252.329C575.31 257.595 567.67 265.169 562.36 274.289L504.908 372.979L392.581 179.993C387.266 170.874 379.623 163.301 370.42 158.036C361.216 152.772 350.777 150 340.151 150C329.525 150 319.086 152.772 309.883 158.036C300.679 163.301 293.036 170.874 287.721 179.993L8.12649 660.002C2.80743 669.12 0.00462935 679.465 5.72978e-06 689.997C-0.00461789 700.529 2.78909 710.876 8.10015 719.999C13.4112 729.121 21.0523 736.696 30.255 741.963C39.4576 747.229 49.8973 750.001 60.524 750H270.538C353.748 750 415.112 713.775 457.336 643.101L559.849 467.145L614.757 372.979L779.547 655.834H559.849L504.908 750ZM267.114 655.737L120.551 655.704L340.249 278.586L449.87 467.145L376.474 593.175C348.433 639.03 316.577 655.737 267.114 655.737Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M504.908 750H839.476C850.103 750.001 860.542 747.229 869.745 741.963C878.948 736.696 886.589 729.121 891.9 719.999C897.211 710.876 900.005 700.529 900 689.997C899.995 679.465 897.193 669.12 891.873 660.002L667.187 274.289C661.876 265.169 654.237 257.595 645.036 252.329C635.835 247.064 625.398 244.291 614.773 244.291C604.149 244.291 593.711 247.064 584.511 252.329C575.31 257.595 567.67 265.169 562.36 274.289L504.908 372.979L392.581 179.993C387.266 170.874 379.623 163.301 370.42 158.036C361.216 152.772 350.777 150 340.151 150C329.525 150 319.086 152.772 309.883 158.036C300.679 163.301 293.036 170.874 287.721 179.993L8.12649 660.002C2.80743 669.12 0.00462935 679.465 5.72978e-06 689.997C-0.00461789 700.529 2.78909 710.876 8.10015 719.999C13.4112 729.121 21.0523 736.696 30.255 741.963C39.4576 747.229 49.8973 750.001 60.524 750H270.538C353.748 750 415.112 713.775 457.336 643.101L559.849 467.145L614.757 372.979L779.547 655.834H559.849L504.908 750ZM267.114 655.737L120.551 655.704L340.249 278.586L449.87 467.145L376.474 593.175C348.433 639.03 316.577 655.737 267.114 655.737Z" fill="#0C0C0D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

187
docs/tailwind.config.ts Normal file
View File

@@ -0,0 +1,187 @@
import type { Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
export default <Partial<Config>> {
darkMode: 'class',
content: [
'docs/content/**/*.md'
],
theme: {
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans]
},
maxWidth: {
'8xl': '90rem'
},
typography: (theme) => {
return {
DEFAULT: {
css: {
'h1, h2, h3, h4': {
fontWeight: theme('fontWeight.bold'),
'scroll-margin-top': 'var(--scroll-mt)'
},
'h1 a, h2 a, h3 a, h4 a': {
borderBottom: 'none !important',
color: 'inherit',
fontWeight: 'inherit'
},
a: {
fontWeight: theme('fontWeight.medium'),
textDecoration: 'none',
borderBottom: '1px solid transparent'
},
'a:hover': {
borderColor: 'var(--tw-prose-links)'
},
'a:has(> code)': {
borderColor: 'transparent !important'
},
'a code': {
color: 'var(--tw-prose-code)',
border: '1px dashed var(--tw-prose-pre-border)'
},
'a:hover code': {
color: 'var(--tw-prose-links)',
borderColor: 'var(--tw-prose-links)'
},
pre: {
margin: '0',
borderRadius: '0.375rem',
border: '1px solid var(--tw-prose-pre-border)',
whiteSpace: 'pre-wrap',
wordBreak: 'break-words'
},
code: {
backgroundColor: 'var(--tw-prose-pre-bg)',
padding: '0.25rem 0.375rem',
borderRadius: '0.375rem',
border: '1px solid var(--tw-prose-pre-border)'
},
'code::before': {
content: ''
},
'code::after': {
content: ''
},
'blockquote p:first-of-type::before': {
content: ''
},
'blockquote p:last-of-type::after': {
content: ''
},
'input[type="checkbox"]': {
color: 'rgb(var(--color-primary-500))',
borderRadius: theme('borderRadius.DEFAULT'),
borderColor: 'rgb(var(--color-gray-300))',
height: theme('spacing.4'),
width: theme('spacing.4'),
marginTop: '-3.5px !important',
marginBottom: '0 !important',
'&:focus': {
'--tw-ring-offset-width': 0
}
},
'input[type="checkbox"]:checked': {
borderColor: 'rgb(var(--color-primary-500))'
},
'input[type="checkbox"]:disabled': {
opacity: 0.5,
cursor: 'not-allowed'
},
'ul.contains-task-list': {
marginLeft: '-1.625em'
},
'ul ul': {
paddingLeft: theme('padding.6')
},
'ul ol': {
paddingLeft: theme('padding.6')
},
'ul > li.task-list-item': {
paddingLeft: '0 !important'
},
'ul > li.task-list-item input': {
marginRight: '7px'
},
'ul > li.task-list-item > ul.contains-task-list': {
marginLeft: 'initial'
},
'ul > li.task-list-item a': {
marginBottom: 0
},
'ul > li.task-list-item::marker': {
content: 'none'
},
'ul > li > p': {
margin: 0
},
'ul > li > span.issue-badge, p > span.issue-badge': {
verticalAlign: 'text-top',
margin: '0 !important'
},
'ul > li > button': {
verticalAlign: 'baseline !important'
},
table: {
wordBreak: 'break-all'
}
}
},
primary: {
css: {
'--tw-prose-body': 'rgb(var(--color-gray-700))',
'--tw-prose-headings': 'rgb(var(--color-gray-900))',
'--tw-prose-lead': 'rgb(var(--color-gray-600))',
'--tw-prose-links': 'rgb(var(--color-primary-500))',
'--tw-prose-bold': 'rgb(var(--color-gray-900))',
'--tw-prose-counters': 'rgb(var(--color-gray-500))',
'--tw-prose-bullets': 'rgb(var(--color-gray-300))',
'--tw-prose-hr': 'rgb(var(--color-gray-100))',
'--tw-prose-quotes': 'rgb(var(--color-gray-900))',
'--tw-prose-quote-borders': 'rgb(var(--color-gray-200))',
'--tw-prose-captions': 'rgb(var(--color-gray-500))',
'--tw-prose-code': 'rgb(var(--color-gray-900))',
'--tw-prose-pre-code': 'rgb(var(--color-gray-900))',
'--tw-prose-pre-bg': 'rgb(var(--color-gray-50))',
'--tw-prose-pre-border': 'rgb(var(--color-gray-200))',
'--tw-prose-th-borders': 'rgb(var(--color-gray-300))',
'--tw-prose-td-borders': 'rgb(var(--color-gray-200))',
'--tw-prose-invert-body': 'rgb(var(--color-gray-200))',
'--tw-prose-invert-headings': theme('colors.white'),
'--tw-prose-invert-lead': 'rgb(var(--color-gray-400))',
'--tw-prose-invert-links': 'rgb(var(--color-primary-400))',
'--tw-prose-invert-bold': theme('colors.white'),
'--tw-prose-invert-counters': 'rgb(var(--color-gray-400))',
'--tw-prose-invert-bullets': 'rgb(var(--color-gray-600))',
'--tw-prose-invert-hr': 'rgb(var(--color-gray-800))',
'--tw-prose-invert-quotes': 'rgb(var(--color-gray-100))',
'--tw-prose-invert-quote-borders': 'rgb(var(--color-gray-700))',
'--tw-prose-invert-captions': 'rgb(var(--color-gray-400))',
'--tw-prose-invert-code': theme('colors.white'),
'--tw-prose-invert-pre-code': theme('colors.white'),
'--tw-prose-invert-pre-bg': 'rgb(var(--color-gray-800))',
'--tw-prose-invert-pre-border': 'rgb(var(--color-gray-700))',
'--tw-prose-invert-th-borders': 'rgb(var(--color-gray-700))',
'--tw-prose-invert-td-borders': 'rgb(var(--color-gray-800))'
}
},
invert: {
css: {
'--tw-prose-pre-border': 'var(--tw-prose-invert-pre-border)',
'input[type="checkbox"]': {
backgroundColor: 'rgb(var(--color-gray-800))',
borderColor: 'rgb(var(--color-gray-700))'
},
'input[type="checkbox"]:checked': {
backgroundColor: 'rgb(var(--color-primary-400))',
borderColor: 'rgb(var(--color-primary-400))'
}
}
}
}
}
}
}
}

3
docs/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@nuxthq/ui",
"version": "0.0.2",
"version": "2.0.0",
"repository": "https://github.com/nuxtlabs/ui",
"license": "MIT",
"exports": {
@@ -14,41 +14,56 @@
"files": [
"dist"
],
"engines": {
"node": ">=18 <19"
},
"scripts": {
"build": "nuxt-module-build",
"prepack": "yarn build",
"dev": "nuxi dev docs",
"build:docs": "nuxi build docs",
"postbuild:docs": "mv ./docs/.vercel_build_output .vercel_build_output",
"lint": "eslint --ext .ts,.js,.vue .",
"typecheck": "nuxi typecheck",
"prepare": "nuxi prepare docs",
"release": "yarn lint && standard-version && git push --follow-tags"
},
"dependencies": {
"@nuxt/kit": "npm:@nuxt/kit-edge@latest",
"@headlessui/vue": "^1.4.3",
"@iconify-json/heroicons-outline": "^1.0.2",
"@iconify-json/heroicons-solid": "^1.0.2",
"@popperjs/core": "^2.11.2",
"@unocss/nuxt": "^0.22.5",
"@vueuse/core": "^7.5.4",
"defu": "^5.0.1",
"gradient-avatar": "^1.0.2",
"@egoist/tailwindcss-icons": "^1.0.7",
"@headlessui/vue": "1.7.10",
"@iconify-json/heroicons": "^1.1.10",
"@nuxt/kit": "^3.4.3",
"@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/tailwindcss": "^6.6.7",
"@popperjs/core": "^2.11.7",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.0.0-insiders.615a228",
"@tailwindcss/typography": "^0.5.9",
"@vueuse/core": "^10.1.2",
"@vueuse/integrations": "^10.1.2",
"@vueuse/math": "^10.1.2",
"defu": "^6.1.2",
"fuse.js": "^6.6.2",
"lodash-es": "^4.17.21",
"nanoid": "^3.2.0",
"pathe": "^0.2.0"
"tailwindcss": "^3.3.2"
},
"devDependencies": {
"@nuxt/module-builder": "^0.1.7",
"@nuxtjs/eslint-config-typescript": "^8.0.0",
"@types/lodash-es": "^4.17.5",
"@vueuse/components": "^7.5.4",
"eslint": "^8.7.0",
"nuxt3": "latest",
"standard-version": "^9.3.2"
"@iconify-json/simple-icons": "^1.1.51",
"@nuxt/content": "^2.6.0",
"@nuxt/module-builder": "^0.3.1",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@nuxtjs/plausible": "^0.2.0",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.16.3",
"@vueuse/nuxt": "^10.1.2",
"eslint": "^8.39.0",
"nuxt": "^3.4.3",
"nuxt-component-meta": "^0.5.1",
"nuxt-lodash": "^2.4.1",
"standard-version": "^9.5.0",
"unbuild": "^1.2.1",
"vue-tsc": "^1.6.3"
},
"build": {
"externals": [
"@unocss/preset-uno"
]
"volta": {
"node": "18.16.0"
}
}

View File

@@ -1,232 +1,221 @@
import { resolve } from 'pathe'
import { defineNuxtModule, installModule, addComponentsDir, addTemplate, addPlugin, resolveModule } from '@nuxt/kit'
import { colors } from '@unocss/preset-uno'
import defu from 'defu'
import type { UnocssNuxtOptions } from '@unocss/nuxt'
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
import colors from 'tailwindcss/colors.js'
import { iconsPlugin, getIconCollections } from '@egoist/tailwindcss-icons'
import { name, version } from '../package.json'
import { colorsAsRegex, excludeColors } from './runtime/utils/colors'
import preset from './runtime/app.config'
import type { DeepPartial } from './runtime/types'
interface ColorsOptions {
/**
* @default 'indigo'
*/
primary?: string
// @ts-ignore
delete colors.lightBlue
// @ts-ignore
delete colors.warmGray
// @ts-ignore
delete colors.trueGray
// @ts-ignore
delete colors.coolGray
// @ts-ignore
delete colors.blueGray
/**
* @default 'zinc'
*/
gray?: string
declare module 'nuxt/schema' {
interface AppConfigInput {
ui?: {
primary?: string
gray?: string
colors?: string[]
} & DeepPartial<typeof preset.ui>
}
}
export interface ModuleOptions {
/**
* @default 'tailwindui'
*/
preset?: string | object
/**
* @default 'u'
*/
prefix?: string
colors?: ColorsOptions
/**
* @default false
*/
global?: boolean
unocss?: UnocssNuxtOptions
}
const defaults = {
preset: 'tailwindui',
prefix: 'u',
colors: {
primary: 'indigo',
gray: 'zinc'
},
unocss: {
shortcuts: [],
rules: [],
variants: [],
theme: {}
}
icons: string[]
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: '@nuxthq/ui',
name,
version,
configKey: 'ui',
compatibility: {
nuxt: '^3.0.0',
bridge: true
nuxt: '^3.0.0-rc.8'
}
},
defaults,
async setup (_options, nuxt) {
const { preset, prefix, colors: { primary = 'indigo', gray = 'zinc' } = {} } = _options
const { shortcuts = [], rules = [], variants = [], theme = {} } = _options.unocss || {}
const options: UnocssNuxtOptions = {
theme: {
colors: {
gray: typeof gray === 'object' ? gray : (colors && colors[gray]),
primary: typeof primary === 'object' ? primary : (colors && colors[primary])
},
...theme
},
preflight: true,
icons: {
prefix: ''
},
shortcuts: {
[`${prefix}-bg-white`]: 'bg-white dark:bg-black',
[`${prefix}-bg-gray-50`]: 'bg-gray-50 dark:bg-gray-900',
[`${prefix}-bg-gray-100`]: 'bg-gray-100 dark:bg-gray-800',
[`${prefix}-bg-gray-200`]: 'bg-gray-200 dark:bg-gray-700',
[`${prefix}-bg-gray-300`]: 'bg-gray-300 dark:bg-gray-600',
[`${prefix}-bg-gray-400`]: 'bg-gray-400 dark:bg-gray-500',
[`${prefix}-bg-gray-500`]: 'bg-gray-500 dark:bg-gray-400',
[`${prefix}-bg-gray-600`]: 'bg-gray-600 dark:bg-gray-300',
[`${prefix}-bg-gray-700`]: 'bg-gray-700 dark:bg-gray-200',
[`${prefix}-bg-gray-800`]: 'bg-gray-800 dark:bg-gray-100',
[`${prefix}-bg-gray-900`]: 'bg-gray-900 dark:bg-gray-50',
[`${prefix}-bg-black`]: 'bg-black dark:bg-white',
[`${prefix}-text-white`]: 'text-white dark:text-black',
[`${prefix}-text-gray-50`]: 'text-gray-50 dark:text-gray-900',
[`${prefix}-text-gray-100`]: 'text-gray-100 dark:text-gray-800',
[`${prefix}-text-gray-200`]: 'text-gray-200 dark:text-gray-700',
[`${prefix}-text-gray-300`]: 'text-gray-300 dark:text-gray-600',
[`${prefix}-text-gray-400`]: 'text-gray-400 dark:text-gray-500',
[`${prefix}-text-gray-500`]: 'text-gray-500 dark:text-gray-400',
[`${prefix}-text-gray-600`]: 'text-gray-600 dark:text-gray-300',
[`${prefix}-text-gray-700`]: 'text-gray-700 dark:text-gray-200',
[`${prefix}-text-gray-800`]: 'text-gray-800 dark:text-gray-100',
[`${prefix}-text-gray-900`]: 'text-gray-900 dark:text-gray-50',
[`${prefix}-text-black`]: 'text-black dark:text-white',
[`${prefix}-border-white`]: 'border-white dark:border-black',
[`${prefix}-border-gray-100`]: 'border-gray-100 dark:border-gray-900',
[`${prefix}-border-gray-200`]: 'border-gray-200 dark:border-gray-800',
[`${prefix}-border-gray-300`]: 'border-gray-300 dark:border-gray-700',
[`${prefix}-border-gray-400`]: 'border-gray-400 dark:border-gray-600',
[`${prefix}-border-gray-500`]: 'border-gray-500 dark:border-gray-500',
[`${prefix}-border-gray-600`]: 'border-gray-600 dark:border-gray-400',
[`${prefix}-border-gray-700`]: 'border-gray-700 dark:border-gray-300',
[`${prefix}-border-gray-800`]: 'border-gray-800 dark:border-gray-200',
[`${prefix}-border-gray-900`]: 'border-gray-900 dark:border-gray-100',
[`${prefix}-border-black`]: 'border-black dark:border-white',
[`${prefix}-divide-white`]: 'divide-white dark:divide-black',
[`${prefix}-divide-gray-100`]: 'divide-gray-100 dark:divide-gray-900',
[`${prefix}-divide-gray-200`]: 'divide-gray-200 dark:divide-gray-800',
[`${prefix}-divide-gray-300`]: 'divide-gray-300 dark:divide-gray-700',
[`${prefix}-divide-gray-400`]: 'divide-gray-400 dark:divide-gray-600',
[`${prefix}-divide-gray-500`]: 'divide-gray-500 dark:divide-gray-500',
[`${prefix}-divide-gray-600`]: 'divide-gray-600 dark:divide-gray-400',
[`${prefix}-divide-gray-700`]: 'divide-gray-700 dark:divide-gray-300',
[`${prefix}-divide-gray-800`]: 'divide-gray-800 dark:divide-gray-200',
[`${prefix}-divide-gray-900`]: 'divide-gray-900 dark:divide-gray-100',
[`${prefix}-divide-black`]: 'divide-black dark:divide-white',
[`${prefix}-ring-white`]: 'ring-white dark:ring-black',
[`${prefix}-ring-gray-100`]: 'ring-gray-100 dark:ring-gray-900',
[`${prefix}-ring-gray-200`]: 'ring-gray-200 dark:ring-gray-800',
[`${prefix}-ring-gray-300`]: 'ring-gray-300 dark:ring-gray-700',
[`${prefix}-ring-gray-400`]: 'ring-gray-400 dark:ring-gray-600',
[`${prefix}-ring-gray-500`]: 'ring-gray-500 dark:ring-gray-500',
[`${prefix}-ring-gray-600`]: 'ring-gray-600 dark:ring-gray-400',
[`${prefix}-ring-gray-700`]: 'ring-gray-700 dark:ring-gray-300',
[`${prefix}-ring-gray-800`]: 'ring-gray-800 dark:ring-gray-200',
[`${prefix}-ring-gray-900`]: 'ring-gray-900 dark:ring-gray-100',
[`${prefix}-ring-black`]: 'ring-black dark:ring-white',
...shortcuts
},
rules: [
[/^shadow-?(.*)$/, ([, d], { theme }) => {
// @ts-ignore
const value = theme?.boxShadow?.[d || 'DEFAULT']
if (value) {
return {
'--un-shadow-color': '0,0,0',
'--un-shadow': value,
'box-shadow': 'var(--un-shadow)'
}
}
}],
...rules
],
variants,
layers: {
icons: 0,
default: 1,
shortcuts: 2
}
}
await installModule('@unocss/nuxt', options, nuxt)
defaults: {
prefix: 'u',
icons: ['heroicons']
},
async setup (options, nuxt) {
const { resolve } = createResolver(import.meta.url)
// Transpile runtime
const runtimeDir = resolve(__dirname, './runtime')
const runtimeDir = resolve('./runtime')
nuxt.options.build.transpile.push(runtimeDir)
nuxt.options.build.transpile.push('@popperjs/core', '@headlessui/vue')
const presetsDir = resolve(runtimeDir, './presets')
nuxt.options.css.push(resolve(runtimeDir, 'ui.css'))
let ui: object = (await import(resolveModule(`./${defaults.preset}`, { paths: presetsDir }))).default
try {
if (typeof preset === 'object') {
ui = defu(preset, ui)
} else {
// @ts-ignore
ui = (await import(resolveModule(`./${preset}`, { paths: presetsDir }))).default
}
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Could not load preset file.')
}
addTemplate({
filename: 'ui.mjs',
getContents: () => `/* @unocss-include */ export default ${JSON.stringify(ui)}`
nuxt.hook('app:resolve', (app) => {
app.configs.push(resolve(runtimeDir, 'app.config.ts'))
})
addPlugin(resolve(runtimeDir, 'plugins', 'toast.client'))
// @ts-ignore
nuxt.hook('tailwindcss:config', function (tailwindConfig: TailwindConfig) {
const globalColors = {
...(tailwindConfig.theme.colors || colors),
...tailwindConfig.theme.extend?.colors
}
tailwindConfig.theme.extend.colors = tailwindConfig.theme.extend.colors || {}
globalColors.primary = tailwindConfig.theme.extend.colors.primary = {
50: 'rgb(var(--color-primary-50) / <alpha-value>)',
100: 'rgb(var(--color-primary-100) / <alpha-value>)',
200: 'rgb(var(--color-primary-200) / <alpha-value>)',
300: 'rgb(var(--color-primary-300) / <alpha-value>)',
400: 'rgb(var(--color-primary-400) / <alpha-value>)',
500: 'rgb(var(--color-primary-500) / <alpha-value>)',
600: 'rgb(var(--color-primary-600) / <alpha-value>)',
700: 'rgb(var(--color-primary-700) / <alpha-value>)',
800: 'rgb(var(--color-primary-800) / <alpha-value>)',
900: 'rgb(var(--color-primary-900) / <alpha-value>)',
950: 'rgb(var(--color-primary-950) / <alpha-value>)'
}
if (globalColors.gray) {
globalColors.cool = tailwindConfig.theme.extend.colors.cool = colors.gray
}
globalColors.gray = tailwindConfig.theme.extend.colors.gray = {
50: 'rgb(var(--color-gray-50) / <alpha-value>)',
100: 'rgb(var(--color-gray-100) / <alpha-value>)',
200: 'rgb(var(--color-gray-200) / <alpha-value>)',
300: 'rgb(var(--color-gray-300) / <alpha-value>)',
400: 'rgb(var(--color-gray-400) / <alpha-value>)',
500: 'rgb(var(--color-gray-500) / <alpha-value>)',
600: 'rgb(var(--color-gray-600) / <alpha-value>)',
700: 'rgb(var(--color-gray-700) / <alpha-value>)',
800: 'rgb(var(--color-gray-800) / <alpha-value>)',
900: 'rgb(var(--color-gray-900) / <alpha-value>)',
950: 'rgb(var(--color-gray-950) / <alpha-value>)'
}
const variantColors = excludeColors(globalColors)
const safeColorsAsRegex = colorsAsRegex(variantColors)
nuxt.options.appConfig.ui = {
...nuxt.options.appConfig.ui,
primary: 'sky',
gray: 'cool',
colors: variantColors
}
tailwindConfig.safelist = tailwindConfig.safelist || []
tailwindConfig.safelist.push(...[{
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|400|500)`)
}, {
pattern: new RegExp(`bg-(${safeColorsAsRegex})-500`),
variants: ['disabled']
}, {
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(400|950)`),
variants: ['dark']
}, {
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(500|900|950)`),
variants: ['dark:hover']
}, {
pattern: new RegExp(`bg-(${safeColorsAsRegex})-400`),
variants: ['dark:disabled']
}, {
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|100|600)`),
variants: ['hover']
}, {
pattern: new RegExp(`outline-(${safeColorsAsRegex})-500`),
variants: ['focus-visible']
}, {
pattern: new RegExp(`outline-(${safeColorsAsRegex})-400`),
variants: ['dark:focus-visible']
}, {
pattern: new RegExp(`ring-(${safeColorsAsRegex})-500`),
variants: ['focus-visible']
}, {
pattern: new RegExp(`ring-(${safeColorsAsRegex})-400`),
variants: ['dark', 'dark:focus-visible']
}, {
pattern: new RegExp(`text-(${safeColorsAsRegex})-400`),
variants: ['dark']
}, {
pattern: new RegExp(`text-(${safeColorsAsRegex})-600`),
variants: ['hover']
}, {
pattern: new RegExp(`text-(${safeColorsAsRegex})-500`),
variants: ['dark:hover']
}])
tailwindConfig.plugins = tailwindConfig.plugins || []
tailwindConfig.plugins.push(iconsPlugin({ collections: getIconCollections(options.icons as any[]) }))
})
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
await installModule('@nuxtjs/tailwindcss', {
viewer: false,
exposeConfig: true,
config: {
darkMode: 'class',
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/typography')
],
content: [
resolve(runtimeDir, 'components/**/*.{vue,js,ts}'),
resolve(runtimeDir, '*.{mjs,js,ts}')
]
}
})
addPlugin({
src: resolve(runtimeDir, 'plugins', 'colors')
})
addComponentsDir({
path: resolve(runtimeDir, 'components', 'elements'),
prefix,
watch: false
})
addComponentsDir({
path: resolve(runtimeDir, 'components', 'feedback'),
prefix,
prefix: options.prefix,
global: options.global,
watch: false
})
addComponentsDir({
path: resolve(runtimeDir, 'components', 'forms'),
prefix,
prefix: options.prefix,
global: options.global,
watch: false
})
addComponentsDir({
path: resolve(runtimeDir, 'components', 'layout'),
prefix,
prefix: options.prefix,
global: options.global,
watch: false
})
addComponentsDir({
path: resolve(runtimeDir, 'components', 'navigation'),
prefix,
prefix: options.prefix,
global: options.global,
watch: false
})
addComponentsDir({
path: resolve(runtimeDir, 'components', 'overlays'),
prefix,
prefix: options.prefix,
global: options.global,
watch: false
})
// Add composables
nuxt.hook('autoImports:dirs', (dirs) => {
dirs.push(resolve(runtimeDir, 'composables'))
})
// Add CSS
nuxt.options.css.push(resolve(runtimeDir, 'css', 'forms.css'))
nuxt.options.vite = {
optimizeDeps: {
include: ['gradient-avatar']
}
}
addImportsDir(resolve(runtimeDir, 'composables'))
}
})

708
src/runtime/app.config.ts Normal file
View File

@@ -0,0 +1,708 @@
// Elements
const avatar = {
wrapper: 'relative inline-flex items-center justify-center',
background: 'bg-gray-100 dark:bg-gray-800',
rounded: 'rounded-full',
placeholder: 'text-xs font-medium leading-none text-gray-900 dark:text-white truncate',
size: {
'3xs': 'h-4 w-4 text-xs',
'2xs': 'h-5 w-5 text-xs',
xs: 'h-6 w-6 text-xs',
sm: 'h-8 w-8 text-sm',
md: 'h-10 w-10 text-md',
lg: 'h-12 w-12 text-lg',
xl: 'h-14 w-14 text-xl',
'2xl': 'h-16 w-16 text-2xl',
'3xl': 'h-20 w-20 text-3xl'
},
chip: {
base: 'absolute block rounded-full ring-2 ring-white dark:ring-gray-900',
position: {
'top-right': 'top-0 right-0',
'bottom-right': 'bottom-0 right-0',
'top-left': 'top-0 left-0',
'bottom-left': 'bottom-0 left-0'
},
variant: {
solid: 'bg-{color}-400'
},
size: {
'3xs': 'h-1 w-1',
'2xs': 'h-1 w-1',
xs: 'h-1.5 w-1.5',
sm: 'h-2 w-2',
md: 'h-2.5 w-2.5',
lg: 'h-3 w-3',
xl: 'h-3.5 w-3.5',
'2xl': 'h-3.5 w-3.5',
'3xl': 'h-4 w-4'
}
},
default: {
size: 'md',
chipVariant: 'solid',
chipPosition: 'top-right'
}
}
const avatarGroup = {
wrapper: 'flex flex-row-reverse',
ring: 'ring-2 ring-white dark:ring-gray-900',
margin: '-mr-1.5 first:mr-0'
}
const badge = {
base: 'inline-flex items-center',
rounded: 'rounded-md',
font: 'font-medium',
size: {
sm: 'text-xs px-1.5 py-0.5',
md: 'text-xs px-2 py-1',
lg: 'text-xs px-2.5 py-1.5'
},
variant: {
solid: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400 ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 ring-opacity-10 dark:ring-opacity-20'
},
default: {
size: 'md',
variant: 'solid',
color: 'primary'
}
}
const button = {
base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75',
font: 'font-medium',
rounded: 'rounded-md',
size: {
'2xs': 'text-xs',
xs: 'text-xs',
sm: 'text-sm',
md: 'text-sm',
lg: 'text-base',
xl: 'text-base'
},
gap: {
'2xs': 'gap-x-1',
xs: 'gap-x-1.5',
sm: 'gap-x-2',
md: 'gap-x-2',
lg: 'gap-x-2',
xl: 'gap-x-2'
},
spacing: {
'2xs': 'px-2 py-1',
xs: 'px-2.5 py-1.5',
sm: 'px-3 py-1.5',
md: 'px-3 py-2',
lg: 'px-4 py-2',
xl: 'px-4 py-3'
},
square: {
'2xs': 'p-[5px]',
xs: 'p-1.5',
sm: 'p-2',
md: 'p-2',
lg: 'p-2.5',
xl: 'p-3'
},
color: {
white: {
solid: 'shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 text-gray-900 dark:text-white bg-white hover:bg-gray-50 disabled:bg-white dark:bg-gray-900 dark:hover:bg-gray-800/50 dark:disabled:bg-gray-900 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
ghost: 'text-gray-900 dark:text-white hover:bg-white dark:hover:bg-gray-900 focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
},
gray: {
solid: 'shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 text-gray-700 dark:text-gray-200 bg-gray-50 hover:bg-gray-100 disabled:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700/50 dark:disabled:bg-gray-800 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
// TODO: For Volta
// 'outline-ghost': 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:ring-1 ring-inset ring-gray-300 dark:ring-gray-700',
ghost: 'text-gray-700 dark:text-gray-200 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800 focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
link: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 underline-offset-4 hover:underline focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
},
black: {
solid: 'shadow-sm text-white dark:text-gray-900 bg-gray-900 hover:bg-gray-800 disabled:bg-gray-900 dark:bg-white dark:hover:bg-gray-100 dark:disabled:bg-white focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
link: 'text-gray-900 dark:text-white underline-offset-4 hover:underline focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
}
},
variant: {
solid: 'shadow-sm text-white dark:text-gray-900 bg-{color}-500 hover:bg-{color}-600 disabled:bg-{color}-500 dark:bg-{color}-400 dark:hover:bg-{color}-500 dark:disabled:bg-{color}-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-{color}-500 dark:focus-visible:outline-{color}-400',
outline: 'ring-1 ring-inset ring-current text-{color}-500 dark:text-{color}-400 hover:bg-{color}-50 dark:hover:bg-{color}-950 focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400',
soft: 'text-{color}-500 dark:text-{color}-400 bg-{color}-50 hover:bg-{color}-100 dark:bg-{color}-950 dark:hover:bg-{color}-900 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400',
ghost: 'text-{color}-500 dark:text-{color}-400 hover:bg-{color}-50 dark:hover:bg-{color}-950 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400',
link: 'text-{color}-500 hover:text-{color}-600 dark:text-{color}-400 dark:hover:text-{color}-500 underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400'
},
icon: {
base: 'flex-shrink-0',
size: {
'2xs': 'h-3.5 w-3.5',
xs: 'h-4 w-4',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-5 w-5',
xl: 'h-6 w-6'
}
},
default: {
size: 'sm',
variant: 'solid',
color: 'primary',
loadingIcon: 'i-heroicons-arrow-path-20-solid'
}
}
const buttonGroup = {
wrapper: 'inline-flex',
rounded: 'rounded-md',
shadow: 'shadow-sm'
}
const dropdown = {
wrapper: 'relative inline-flex text-left',
container: 'z-20',
width: 'w-48',
background: 'bg-white dark:bg-gray-800',
shadow: 'shadow-lg',
rounded: 'rounded-md',
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
base: 'focus:outline-none',
divide: 'divide-y divide-gray-200 dark:divide-gray-700',
spacing: 'p-1',
item: {
base: 'group flex items-center gap-2 px-2 py-1.5 text-sm w-full rounded-md',
active: 'bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-white',
inactive: 'text-gray-700 dark:text-gray-200',
disabled: 'cursor-not-allowed opacity-50',
icon: {
base: 'flex-shrink-0 h-4 w-4',
active: 'text-gray-500 dark:text-gray-400',
inactive: 'text-gray-400 dark:text-gray-500'
},
avatar: {
base: 'flex-shrink-0',
size: '3xs'
},
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold text-gray-500 dark:text-gray-400 ml-auto'
},
transition: {
enterActiveClass: 'transition duration-100 ease-out',
enterFromClass: 'transform scale-95 opacity-0',
enterToClass: 'transform scale-100 opacity-100',
leaveActiveClass: 'transition duration-75 ease-out',
leaveFromClass: 'transform scale-100 opacity-100',
leaveToClass: 'transform scale-95 opacity-0'
},
popper: {
placement: 'bottom-end',
strategy: 'fixed'
}
}
// Forms
const input = {
wrapper: 'relative',
base: 'relative block w-full disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none',
custom: '',
size: {
'2xs': 'text-xs',
xs: 'text-xs',
sm: 'text-sm',
md: 'text-sm',
lg: 'text-base',
xl: 'text-base'
},
gap: {
'2xs': 'gap-x-1',
xs: 'gap-x-1.5',
sm: 'gap-x-2',
md: 'gap-x-2',
lg: 'gap-x-2',
xl: 'gap-x-2'
},
spacing: {
'2xs': 'px-2 py-1',
xs: 'px-2.5 py-1.5',
sm: 'px-3 py-1.5',
md: 'px-3 py-2',
lg: 'px-4 py-2',
xl: 'px-4 py-3'
},
leading: {
spacing: {
'2xs': 'pl-[26px]',
xs: 'pl-8',
sm: 'pl-9',
md: 'pl-10',
lg: 'pl-11',
xl: 'pl-12'
}
},
trailing: {
spacing: {
'2xs': 'pr-[26px]',
xs: 'pr-8',
sm: 'pr-9',
md: 'pr-10',
lg: 'pr-11',
xl: 'pr-12'
}
},
appearance: {
white: 'border-0 bg-white dark:bg-gray-900 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
gray: 'border-0 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none'
},
icon: {
base: 'text-gray-400 dark:text-gray-500',
size: {
'2xs': 'h-3.5 w-3.5',
xs: 'h-4 w-4',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-5 w-5',
xl: 'h-6 w-6'
},
leading: {
wrapper: 'absolute inset-y-0 left-0 flex items-center pointer-events-none',
spacing: {
'2xs': 'pl-2',
xs: 'pl-2.5',
sm: 'pl-3',
md: 'pl-3',
lg: 'pl-4',
xl: 'pl-4'
}
},
trailing: {
wrapper: 'absolute inset-y-0 right-0 flex items-center pointer-events-none',
spacing: {
'2xs': 'pr-2',
xs: 'pr-2.5',
sm: 'pr-3',
md: 'pr-3',
lg: 'pr-4',
xl: 'pr-4'
}
}
},
default: {
size: 'sm',
appearance: 'white',
loadingIcon: 'i-heroicons-arrow-path-20-solid'
}
}
const inputGroup = {
wrapper: '',
label: 'block text-sm font-medium text-gray-700 dark:text-gray-200',
labelWrapper: 'flex content-center justify-between',
container: 'mt-1 relative',
required: 'text-red-400',
description: 'text-sm leading-5 text-gray-500 dark:text-gray-400',
hint: 'text-sm leading-5 text-gray-500 dark:text-gray-400',
help: 'mt-2 text-sm text-gray-500 dark:text-gray-400'
}
const textarea = {
...input
}
const select = {
...input
}
const selectMenu = {
wrapper: 'relative',
container: 'z-20',
width: 'w-full',
height: 'max-h-60',
base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
background: 'bg-white dark:bg-gray-800',
shadow: 'shadow-lg',
rounded: 'rounded-md',
spacing: 'p-1',
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 u-text-gray-700 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 focus:border-inherit sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500',
option: {
base: 'cursor-default select-none relative px-2 py-1.5 rounded-md text-sm text-gray-900 dark:text-white flex items-center justify-between gap-1',
container: 'flex items-center gap-2',
active: 'bg-gray-100 dark:bg-gray-900',
inactive: '',
disabled: 'cursor-not-allowed opacity-50',
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
icon: {
base: 'flex-shrink-0 h-4 w-4',
active: 'text-gray-900 dark:text-white',
inactive: 'text-gray-400 dark:text-gray-500'
},
avatar: {
base: 'flex-shrink-0',
size: '3xs'
},
chip: {
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
},
selected: {
wrapper: 'absolute inset-y-0 right-0 flex items-center pr-2',
icon: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
}
},
transition: {
leaveActiveClass: 'transition ease-in duration-100',
leaveFromClass: 'opacity-100',
leaveToClass: 'opacity-0'
},
popper: {
placement: 'bottom-end'
},
default: {
selectedIcon: 'i-heroicons-check-20-solid'
}
}
const radio = {
wrapper: 'relative flex items-start',
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus:ring-2 focus:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus:ring-primary-500 dark:focus:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed',
label: 'font-medium text-gray-700 dark:text-gray-200',
required: 'text-red-400',
help: 'text-gray-500 dark:text-gray-400'
}
const checkbox = {
...radio,
base: radio.base + ' rounded'
}
const toggle = {
base: 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:focus:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900',
active: 'bg-primary-500 dark:bg-primary-400',
inactive: 'bg-gray-200 dark:bg-gray-700',
container: {
base: 'pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
active: 'translate-x-5',
inactive: 'translate-x-0'
},
icon: {
base: 'absolute inset-0 h-full w-full flex items-center justify-center transition-opacity',
active: 'opacity-100 ease-in duration-200',
inactive: 'opacity-0 ease-out duration-100',
on: 'h-3 w-3 text-primary-500 dark:text-primary-400',
off: 'h-3 w-3 text-gray-400 dark:text-gray-500'
}
}
// Layout
const card = {
base: 'overflow-hidden',
background: 'bg-white dark:bg-gray-900',
divide: 'divide-y divide-gray-200 dark:divide-gray-700',
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
rounded: 'rounded-lg',
shadow: 'shadow',
body: {
base: '',
background: '',
spacing: 'px-4 py-5 sm:p-6'
},
header: {
base: '',
background: '',
spacing: 'px-4 py-5 sm:px-6'
},
footer: {
base: '',
background: '',
spacing: 'px-4 py-4 sm:px-6'
}
}
const container = {
base: 'mx-auto',
spacing: 'px-4 sm:px-6 lg:px-8',
constrained: 'max-w-7xl'
}
// Navigation
const verticalNavigation = {
wrapper: 'relative z-0',
base: 'group flex items-center gap-2 text-sm font-medium rounded-md w-full relative focus:outline-none after:absolute after:inset-px after:z-[-1] after:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
spacing: 'px-3 py-1.5',
active: 'u-text-gray-900 after:bg-gray-100 dark:after:bg-gray-800',
inactive: 'u-text-gray-500 hover:u-text-gray-900 hover:after:bg-gray-50 dark:hover:after:bg-gray-800/50 focus-visible:after:bg-gray-50 dark:focus-visible:after:bg-gray-800/50',
icon: {
base: 'flex-shrink-0 w-4 h-4',
active: 'u-text-gray-700',
inactive: 'u-text-gray-400 group-hover:u-text-gray-700'
},
avatar: {
base: 'flex-shrink-0',
size: '3xs'
},
badge: {
base: 'ml-auto inline-block py-0.5 px-2 text-xs rounded-md -mr-1 -my-0.5',
active: 'bg-white dark:bg-gray-900',
inactive: 'u-bg-gray-100 u-text-gray-600 group-hover:bg-white dark:group-hover:bg-gray-900'
}
}
const commandPalette = {
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800',
container: 'relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2',
input: {
wrapper: 'relative flex items-center',
base: 'w-full h-12 px-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0 sm:text-sm',
spacing: 'pl-10',
icon: 'pointer-events-none absolute left-4 h-4 w-4 text-gray-400 dark:text-gray-500',
close: 'absolute right-4'
},
empty: {
wrapper: 'flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14',
label: 'text-sm text-center text-gray-900 dark:text-white',
queryLabel: 'text-sm text-center text-gray-900 dark:text-white',
icon: 'w-6 h-6 mx-auto text-gray-400 dark:text-gray-500 mb-4'
},
group: {
wrapper: 'p-2',
label: 'px-2 my-2 text-xs font-semibold text-gray-900 dark:text-white',
container: 'text-sm text-gray-700 dark:text-gray-200',
command: {
base: 'flex justify-between select-none items-center rounded-md px-2 py-1.5 gap-2 relative',
active: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white',
inactive: '',
label: 'flex items-center gap-1.5 min-w-0',
prefix: 'text-gray-400 dark:text-gray-500',
suffix: 'text-gray-400 dark:text-gray-500',
container: 'flex items-center gap-2 min-w-0',
icon: {
base: 'flex-shrink-0 w-4 h-4',
active: 'text-gray-900 dark:text-white',
inactive: 'text-gray-400 dark:text-gray-500'
},
avatar: {
base: 'flex-shrink-0',
size: '3xs'
},
chip: {
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
},
disabled: 'opacity-50',
selected: {
icon: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
},
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold text-gray-500 dark:text-gray-400'
},
active: 'flex-shrink-0 text-gray-500 dark:text-gray-400',
inactive: 'flex-shrink-0 text-gray-500 dark:text-gray-400'
},
default: {
icon: 'i-heroicons-magnifying-glass-20-solid',
empty: {
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.'
},
close: null,
selectedIcon: 'i-heroicons-check-20-solid'
}
}
// Overlays
const modal = {
wrapper: 'relative z-50',
inner: 'fixed inset-0 overflow-y-auto',
container: 'flex min-h-full items-end sm:items-center justify-center text-center',
spacing: 'p-4 sm:p-0',
base: 'relative text-left overflow-hidden sm:my-8 w-full flex flex-col',
overlay: {
base: 'fixed inset-0 transition-opacity',
background: 'bg-gray-500/75 dark:bg-gray-600/75',
transition: {
enter: 'ease-out duration-300',
enterFrom: 'opacity-0',
enterTo: 'opacity-100',
leave: 'ease-in duration-200',
leaveFrom: 'opacity-100',
leaveTo: 'opacity-0'
}
},
background: 'bg-white dark:bg-gray-900',
ring: '',
rounded: 'rounded-lg',
shadow: 'shadow-xl',
width: 'sm:max-w-lg',
height: '',
transition: {
enter: 'ease-out duration-300',
enterFrom: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95',
enterTo: 'opacity-100 translate-y-0 sm:scale-100',
leave: 'ease-in duration-200',
leaveFrom: 'opacity-100 translate-y-0 sm:scale-100',
leaveTo: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
}
}
const slideover = {
wrapper: 'fixed inset-0 flex z-50',
overlay: {
base: 'fixed inset-0 transition-opacity',
background: 'bg-gray-500/75 dark:bg-gray-600/75',
transition: {
enter: 'ease-in-out duration-500',
enterFrom: 'opacity-0',
enterTo: 'opacity-100',
leave: 'ease-in-out duration-500',
leaveFrom: 'opacity-100',
leaveTo: 'opacity-0'
}
},
base: 'relative flex-1 flex flex-col w-full focus:outline-none',
background: 'bg-white dark:bg-gray-900',
ring: '',
rounded: '',
shadow: 'shadow-xl',
width: 'w-screen max-w-md',
transition: {
enter: 'transform transition ease-in-out duration-500 sm:duration-700',
leave: 'transform transition ease-in-out duration-500 sm:duration-700'
}
}
const tooltip = {
wrapper: 'relative inline-flex',
container: 'z-20',
width: 'max-w-xs',
background: 'bg-white dark:bg-gray-900',
shadow: 'shadow',
rounded: 'rounded',
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
base: 'invisible lg:visible h-6 px-2 py-1 text-xs font-normal truncate',
shortcuts: 'hidden md:inline-flex items-center justify-end flex-shrink-0 gap-0.5 ml-1',
transition: {
enterActiveClass: 'transition ease-out duration-200',
enterFromClass: 'opacity-0 translate-y-1',
enterToClass: 'opacity-100 translate-y-0',
leaveActiveClass: 'transition ease-in duration-150',
leaveFromClass: 'opacity-100 translate-y-0',
leaveToClass: 'opacity-0 translate-y-1'
},
popper: {
strategy: 'fixed'
}
}
const popover = {
wrapper: 'relative',
container: 'z-20',
width: '',
background: 'bg-white dark:bg-gray-900',
shadow: 'shadow-lg',
rounded: 'rounded-md',
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
base: 'overflow-hidden focus:outline-none',
transition: {
enterActiveClass: 'transition ease-out duration-200',
enterFromClass: 'opacity-0 translate-y-1',
enterToClass: 'opacity-100 translate-y-0',
leaveActiveClass: 'transition ease-in duration-150',
leaveFromClass: 'opacity-100 translate-y-0',
leaveToClass: 'opacity-0 translate-y-1'
},
popper: {
strategy: 'fixed'
}
}
const contextMenu = {
wrapper: 'relative',
container: 'z-20',
width: '',
background: 'bg-white dark:bg-gray-900',
shadow: 'shadow-lg',
rounded: 'rounded-md',
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
base: 'overflow-hidden focus:outline-none',
transition: {
enterActiveClass: 'transition ease-out duration-200',
enterFromClass: 'opacity-0 translate-y-1',
enterToClass: 'opacity-100 translate-y-0',
leaveActiveClass: 'transition ease-in duration-150',
leaveFromClass: 'opacity-100 translate-y-0',
leaveToClass: 'opacity-0 translate-y-1'
},
popper: {
placement: 'bottom-start',
scroll: false
}
}
const notification = {
wrapper: 'w-full pointer-events-auto',
container: 'relative overflow-hidden',
title: 'text-sm font-medium text-gray-900 dark:text-white',
description: 'mt-1 text-sm leading-5 text-gray-500 dark:text-gray-400',
background: 'bg-white dark:bg-gray-900',
shadow: 'shadow-lg',
rounded: 'rounded-lg',
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
icon: 'flex-shrink-0 w-5 h-5 text-gray-900 dark:text-white',
avatar: 'flex-shrink-0 pt-0.5',
progress: 'absolute bottom-0 left-0 right-0 h-1 bg-primary-500 dark:bg-primary-400',
transition: {
enterActiveClass: 'transform ease-out duration-300 transition',
enterFromClass: 'translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2',
enterToClass: 'translate-y-0 opacity-100 sm:translate-x-0',
leaveActiveClass: 'transition ease-in duration-100',
leaveFromClass: 'opacity-100',
leaveToClass: 'opacity-0'
},
default: {
close: {
icon: 'i-heroicons-x-mark-20-solid',
color: 'gray',
variant: 'link',
padded: false
},
action: {
size: 'xs',
color: 'white'
}
}
}
const notifications = {
wrapper: 'fixed bottom-0 right-0 flex flex-col justify-end w-full z-[55] sm:w-96',
container: 'px-4 sm:px-6 py-6 space-y-3 overflow-y-auto'
}
export default {
ui: {
avatar,
avatarGroup,
badge,
button,
buttonGroup,
dropdown,
input,
inputGroup,
textarea,
select,
selectMenu,
checkbox,
radio,
toggle,
card,
container,
verticalNavigation,
commandPalette,
modal,
slideover,
popover,
tooltip,
contextMenu,
notification,
notifications
}
}

View File

@@ -1,165 +1,135 @@
<template>
<span class="relative inline-flex items-center justify-center" :class="avatarClass" @click="goto">
<img v-if="url" :src="url" :alt="alt" :class="[sizeClass, roundedClass]">
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-else-if="gradientPlaceholder" class="w-full h-full overflow-hidden" :class="roundedClass" v-html="gradientPlaceholder" />
<span
v-else-if="placeholder"
class="font-medium leading-none u-text-gray-900 uppercase"
>{{ placeholder }}</span>
<span
v-else-if="text"
>{{ text }}</span>
<svg
v-else
class="w-full h-full u-text-gray-300"
:class="roundedClass"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
<span :class="wrapperClass">
<img v-if="url && !error" :class="avatarClass" :src="url" :alt="alt" :onerror="() => onError()">
<span v-else-if="text || placeholder" :class="ui.placeholder">{{ text || placeholder }}</span>
<span
v-if="status"
class="absolute top-0 right-0 block rounded-full ring-1 u-ring-white"
:class="statusClass"
/>
<span v-if="chipColor" :class="chipClass" />
<slot />
</span>
</template>
<script>
import avatar from 'gradient-avatar'
<script lang="ts">
import { defineComponent, ref, computed, watch } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
export default {
// const appConfig = useAppConfig()
export default defineComponent({
props: {
src: {
type: [String, Boolean],
default: null
},
text: {
type: String,
default: null
},
alt: {
type: String,
default: null
},
to: {
text: {
type: String,
default: null
},
size: {
type: String,
default: 'md',
validator (value) {
return ['xxxs', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'].includes(value)
default: () => appConfig.ui.avatar.default.size,
validator (value: string) {
return Object.keys(appConfig.ui.avatar.size).includes(value)
}
},
rounded: {
type: Boolean,
default: false
},
gradient: {
type: Boolean,
default: false
},
status: {
chipColor: {
type: String,
default: null,
validator (value) {
return ['online', 'idle', 'invisible', 'donotdisturb', 'focus'].includes(value)
validator (value: string) {
return appConfig.ui.colors.includes(value)
}
},
chipVariant: {
type: String,
default: () => appConfig.ui.avatar.default.chipVariant,
validator (value: string) {
return Object.keys(appConfig.ui.avatar.chip.variant).includes(value)
}
},
chipPosition: {
type: String,
default: () => appConfig.ui.avatar.default.chipPosition,
validator (value: string) {
return Object.keys(appConfig.ui.avatar.chip.position).includes(value)
}
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.avatar>>,
default: () => appConfig.ui.avatar
}
},
computed: {
url () {
if (typeof this.src === 'boolean') {
setup (props) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.avatar>>(() => defu({}, props.ui, appConfig.ui.avatar))
const wrapperClass = computed(() => {
return classNames(
ui.value.wrapper,
ui.value.background,
ui.value.rounded,
ui.value.size[props.size]
)
})
const avatarClass = computed(() => {
return classNames(
ui.value.rounded,
ui.value.size[props.size]
)
})
const chipClass = computed(() => {
return classNames(
ui.value.chip.base,
ui.value.chip.size[props.size],
ui.value.chip.position[props.chipPosition],
ui.value.chip.variant[props.chipVariant]?.replaceAll('{color}', props.chipColor)
)
})
const url = computed(() => {
if (typeof props.src === 'boolean') {
return null
}
return this.src
},
placeholder () {
if (!this.alt) {
return
}
return props.src
})
return this.alt.split(' ').map(word => word.charAt(0)).join('')
},
gradientPlaceholder () {
if (!this.gradient) {
return
}
const placeholder = computed(() => {
return (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2)
})
return avatar(this.alt || new Date().toString())
},
sizeClass () {
return ({
xxxs: 'h-4 w-4 text-xs',
xxs: 'h-5 w-5 text-xs',
xs: 'h-6 w-6 text-xs',
sm: 'h-8 w-8 text-sm',
md: 'h-10 w-10 text-md',
lg: 'h-12 w-12 text-lg',
xl: 'h-14 w-14 text-xl',
'2xl': 'h-16 w-16 text-2xl',
'3xl': 'h-20 w-20 text-3xl'
})[this.size]
},
roundedClass () {
return ({
true: 'rounded-lg',
false: 'rounded-full'
})[this.rounded]
},
placeholderClass () {
return ({
true: 'u-bg-gray-100',
false: 'u-bg-gray-100'
})[!!this.alt]
},
avatarClass () {
return [
this.sizeClass,
this.roundedClass,
this.placeholderClass,
this.to ? 'cursor-pointer' : ''
].join(' ')
},
statusClass () {
return [
({
online: 'bg-green-400',
idle: 'bg-yellow-400',
invisible: 'u-bg-gray-300',
donotdisturb: 'bg-red-400',
focus: 'bg-primary-500'
})[this.status],
({
xxxs: 'h-1 w-1',
xxs: 'h-1 w-1',
xs: 'h-1.5 w-1.5',
sm: 'h-2 w-2',
md: 'h-2.5 w-2.5',
lg: 'h-3 w-3',
xl: 'h-3.5 w-3.5',
'2xl': 'h-3.5 w-3.5',
'3xl': 'h-4 w-4'
})[this.size],
({
true: 'transform -translate-y-1/2 translate-x-1/2'
})[this.rounded]
].join(' ')
const error = ref(false)
watch(() => props.src, () => {
if (error.value) {
error.value = false
}
})
function onError () {
error.value = true
}
},
methods: {
goto (e) {
if (!this.to || !this.$router) { return }
e.preventDefault()
this.$router.push(this.to)
return {
wrapperClass,
avatarClass,
chipClass,
url,
placeholder,
error,
onError
}
}
}
})
</script>

View File

@@ -0,0 +1,80 @@
import { h, computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import Avatar from './Avatar.vue'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
// const appConfig = useAppConfig()
export default defineComponent({
props: {
size: {
type: String,
default: null,
validator (value: string) {
return Object.keys(appConfig.ui.avatar.size).includes(value)
}
},
max: {
type: Number,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.avatarGroup>>,
default: () => appConfig.ui.avatarGroup
}
},
setup (props, { slots }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.avatarGroup>>(() => defu({}, props.ui, appConfig.ui.avatarGroup))
const children = computed(() => {
let children = slots.default?.()
// @ts-ignore-next
if (children.length && children[0].type.name === 'ContentSlot') {
// @ts-ignore-next
children = children[0].ctx.slots.default?.()
}
return children
})
const max = computed(() => typeof props.max === 'string' ? parseInt(props.max, 10) : props.max)
const clones = computed(() => children.value.map((node, index) => {
if (!props.max || (max.value && index < max.value)) {
if (props.size) {
node.props.size = props.size
}
node.props.class = node.props.class || ''
node.props.class += ` ${classNames(
ui.value.ring,
ui.value.margin
)}`
return node
}
if (max.value !== undefined && index === max.value) {
return h(Avatar, {
size: props.size,
text: `+${children.value.length - max.value}`,
class: classNames(
ui.value.ring,
ui.value.margin
)
})
}
return null
}).filter(Boolean).reverse())
return () => h('div', { class: ui.value.wrapper }, clones.value)
}
})

View File

@@ -1,62 +0,0 @@
<template>
<div class="flex">
<Avatar
v-for="(avatar, index) of avatars"
:key="index"
:src="avatar.src"
class="ring-2 u-ring-white -ml-1.5 first:ml-0"
:size="size"
:status="avatar.status"
/>
<Avatar
v-if="remainingGroupSize > 0"
class="ring-2 u-ring-white -ml-1.5 first:ml-0 text-[10px]"
:size="size"
:text="`+${remainingGroupSize}`"
/>
</div>
</template>
<script>
import Avatar from './Avatar'
export default {
components: {
Avatar
},
props: {
group: {
type: Array,
default: () => []
},
size: {
type: String,
default: 'md',
validator (value) {
return ['xxxs', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
}
},
max: {
type: Number,
default: null
}
},
computed: {
avatars () {
return this.group.map((avatar) => {
return typeof avatar === 'string' ? { src: avatar } : avatar
})
},
displayedGroup () {
if (!this.max) { return this.avatars }
return this.avatars.slice(0, this.max)
},
remainingGroupSize () {
if (!this.max) { return 0 }
return this.avatars.length - this.max
}
}
}
</script>

View File

@@ -4,47 +4,63 @@
</span>
</template>
<script>
import { computed } from 'vue'
<script lang="ts">
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import $ui from '#build/ui'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
export default {
// const appConfig = useAppConfig()
export default defineComponent({
props: {
size: {
type: String,
default: 'md',
validator (value) {
return Object.keys($ui.badge.size).includes(value)
default: () => appConfig.ui.badge.default.size,
validator (value: string) {
return Object.keys(appConfig.ui.badge.size).includes(value)
}
},
color: {
type: String,
default: () => appConfig.ui.badge.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
}
},
variant: {
type: String,
default: 'primary',
validator (value) {
return Object.keys($ui.badge.variant).includes(value)
default: () => appConfig.ui.badge.default.variant,
validator (value: string) {
return Object.keys(appConfig.ui.badge.variant).includes(value)
}
},
baseClass: {
type: String,
default: () => $ui.badge.base
},
rounded: {
type: Boolean,
default: false
},
label: {
type: String,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.badge>>,
default: () => appConfig.ui.badge
}
},
setup (props) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.badge>>(() => defu({}, props.ui, appConfig.ui.badge))
const badgeClass = computed(() => {
return classNames(
props.baseClass,
$ui.badge.size[props.size],
$ui.badge.variant[props.variant],
props.rounded ? 'rounded-full' : 'rounded-md'
ui.value.base,
ui.value.font,
ui.value.rounded,
ui.value.size[props.size],
ui.value.variant[props.variant]?.replaceAll('{color}', props.color)
)
})
@@ -52,5 +68,5 @@ export default {
badgeClass
}
}
}
})
</script>

View File

@@ -6,23 +6,34 @@
:aria-label="ariaLabel"
v-bind="buttonProps"
>
<Icon v-if="isLeading" :name="iconName" :class="iconClass" aria-hidden="true" />
<slot><span :class="truncate ? 'text-left break-all line-clamp-1' : ''">{{ label }}</span></slot>
<Icon v-if="isTrailing" :name="iconName" :class="iconClass" aria-hidden="true" />
<Icon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
<slot>
<span v-if="label" :class="[truncate ? 'text-left break-all line-clamp-1' : '']">
{{ label }}
</span>
</slot>
<Icon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
</component>
</template>
<script>
import { ref, computed } from 'vue'
import Link from '../elements/Link'
import Icon from '../elements/Icon'
<script lang="ts">
import { ref, computed, defineComponent, useSlots } from 'vue'
import type { PropType } from 'vue'
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import $ui from '#build/ui'
import { NuxtLink } from '#components'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
export default {
// const appConfig = useAppConfig()
export default defineComponent({
components: {
Icon,
Link
Icon
},
props: {
type: {
@@ -45,18 +56,29 @@ export default {
type: Boolean,
default: false
},
padded: {
type: Boolean,
default: true
},
size: {
type: String,
default: 'md',
validator (value) {
return Object.keys($ui.button.size).includes(value)
default: () => appConfig.ui.button.default.size,
validator (value: string) {
return Object.keys(appConfig.ui.button.size).includes(value)
}
},
color: {
type: String,
default: () => appConfig.ui.button.default.color,
validator (value: string) {
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.button.color)].includes(value)
}
},
variant: {
type: String,
default: 'primary',
validator (value) {
return Object.keys($ui.button.variant).includes(value)
default: () => appConfig.ui.button.default.variant,
validator (value: string) {
return Object.keys(appConfig.ui.button.variant).includes(value)
}
},
icon: {
@@ -65,7 +87,15 @@ export default {
},
loadingIcon: {
type: String,
default: () => $ui.button.icon.loading
default: () => appConfig.ui.button.default.loadingIcon
},
leadingIcon: {
type: String,
default: null
},
trailingIcon: {
type: String,
default: null
},
trailing: {
type: Boolean,
@@ -76,7 +106,7 @@ export default {
default: false
},
to: {
type: [String, Object],
type: [String, Object] as PropType<string | RouteLocationNormalized | RouteLocationRaw>,
default: null
},
target: {
@@ -87,22 +117,6 @@ export default {
type: String,
default: null
},
rounded: {
type: Boolean,
default: false
},
baseClass: {
type: String,
default: () => $ui.button.base
},
iconBaseClass: {
type: String,
default: () => $ui.button.icon.base
},
customClass: {
type: String,
default: null
},
square: {
type: Boolean,
default: false
@@ -110,76 +124,108 @@ export default {
truncate: {
type: Boolean,
default: false
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.button>>,
default: () => appConfig.ui.button
}
},
setup (props, { slots }) {
setup (props) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.button>>(() => defu({}, props.ui, appConfig.ui.button))
const slots = useSlots()
const button = ref(null)
const buttonIs = computed(() => {
if (props.to) {
return 'Link'
return NuxtLink
}
return 'button'
})
const buttonProps = computed(() => {
switch (buttonIs.value) {
case 'Link': return { to: props.to, target: props.target }
default: return { disabled: props.disabled || props.loading, type: props.type }
if (props.to) {
return { to: props.to, target: props.target }
} else {
return { disabled: props.disabled || props.loading, type: props.type }
}
})
const isLeading = computed(() => {
return (props.icon && props.leading) || (props.icon && !props.trailing) || (props.loading && !props.trailing)
return (props.icon && props.leading) || (props.icon && !props.trailing) || (props.loading && !props.trailing) || props.leadingIcon
})
const isTrailing = computed(() => {
return (props.icon && props.trailing) || (props.loading && props.trailing)
return (props.icon && props.trailing) || (props.loading && props.trailing) || props.trailingIcon
})
const isSquare = computed(() => props.square || (!slots.default && !props.label))
const buttonClass = computed(() => {
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
return classNames(
props.baseClass,
$ui.button.size[props.size],
$ui.button[isSquare.value ? 'square' : 'spacing'][props.size],
$ui.button.variant[props.variant],
props.block ? 'w-full flex justify-center items-center' : 'inline-flex items-center',
props.rounded ? 'rounded-full' : 'rounded-md',
props.customClass
ui.value.base,
ui.value.font,
ui.value.rounded,
ui.value.size[props.size],
ui.value.gap[props.size],
props.padded && ui.value[isSquare.value ? 'square' : 'spacing'][props.size],
variant?.replaceAll('{color}', props.color),
props.block ? 'w-full flex justify-center items-center' : 'inline-flex items-center'
)
})
const iconName = computed(() => {
const leadingIconName = computed(() => {
if (props.loading) {
return props.loadingIcon
}
return props.icon
return props.leadingIcon || props.icon
})
const iconClass = computed(() => {
const trailingIconName = computed(() => {
if (props.loading && !isLeading.value) {
return props.loadingIcon
}
return props.trailingIcon || props.icon
})
const leadingIconClass = computed(() => {
return classNames(
props.iconBaseClass,
$ui.input.icon.size[props.size],
isLeading.value && (!!slots.default || !!props.label?.length) && $ui.button.icon.leading.spacing[props.size],
isTrailing.value && (!!slots.default || !!props.label?.length) && $ui.button.icon.trailing.spacing[props.size],
ui.value.icon.base,
ui.value.icon.size[props.size],
props.loading && 'animate-spin'
)
})
const trailingIconClass = computed(() => {
return classNames(
ui.value.icon.base,
ui.value.icon.size[props.size],
props.loading && !isLeading.value && 'animate-spin'
)
})
return {
button,
buttonIs,
buttonProps,
buttonClass,
isLeading,
isTrailing,
iconName,
iconClass
isSquare,
buttonClass,
leadingIconName,
trailingIconName,
leadingIconClass,
trailingIconClass
}
}
}
})
</script>

View File

@@ -0,0 +1,80 @@
import { h, computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
// const appConfig = useAppConfig()
export default defineComponent({
props: {
size: {
type: String,
default: null,
validator (value: string) {
return Object.keys(appConfig.ui.avatar.size).includes(value)
}
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.buttonGroup>>,
default: () => appConfig.ui.buttonGroup
}
},
setup (props, { slots }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.buttonGroup>>(() => defu({}, props.ui, appConfig.ui.buttonGroup))
const children = computed(() => {
let children = slots.default?.()
// @ts-ignore-next
if (children.length && children[0].type.name === 'ContentSlot') {
// @ts-ignore-next
children = children[0].ctx.slots.default?.()
}
return children
})
const rounded = computed(() => ({
'rounded-none': { left: 'rounded-l-none', right: 'rounded-r-none' },
'rounded-sm': { left: 'rounded-l-sm', right: 'rounded-r-sm' },
rounded: { left: 'rounded-l', right: 'rounded-r' },
'rounded-md': { left: 'rounded-l-md', right: 'rounded-r-md' },
'rounded-lg': { left: 'rounded-l-lg', right: 'rounded-r-lg' },
'rounded-xl': { left: 'rounded-l-xl', right: 'rounded-r-xl' },
'rounded-2xl': { left: 'rounded-l-2xl', right: 'rounded-r-2xl' },
'rounded-3xl': { left: 'rounded-l-3xl', right: 'rounded-r-3xl' },
'rounded-full': { left: 'rounded-l-full', right: 'rounded-r-full' }
}[ui.value.rounded]))
const clones = computed(() => children.value.map((node, index) => {
if (props.size) {
node.props.size = props.size
}
node.props.class = node.props.class || ''
node.props.class += ' !shadow-none'
node.props.ui = node.props.ui || {}
node.props.ui.rounded = ''
if (index === 0) {
node.props.ui.rounded = rounded.value.left
}
if (index > 0) {
node.props.class += ' -ml-px'
}
if (index === children.value.length - 1) {
node.props.ui.rounded = rounded.value.right
}
return node
}))
return () => h('div', { class: [ui.value.wrapper, ui.value.rounded, ui.value.shadow] }, clones.value)
}
})

View File

@@ -1,29 +1,40 @@
<template>
<Menu v-slot="{ open }" as="div" :class="wrapperClass">
<MenuButton ref="trigger" as="div">
<slot :open="open">
<button>Open</button>
<Menu v-slot="{ open }" as="div" :class="ui.wrapper" @mouseleave="onMouseLeave">
<MenuButton
ref="trigger"
as="div"
:disabled="disabled"
class="inline-flex w-full"
role="button"
@mouseover="onMouseOver"
>
<slot :open="open" :disabled="disabled">
<button :disabled="disabled">
Open
</button>
</slot>
</MenuButton>
<div v-if="open" ref="container" :class="containerClass">
<transition
appear
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-out"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<MenuItems :class="itemsClass" static>
<div v-for="(subItems, index) of items" :key="index" class="py-1">
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }">
<Component v-bind="item" :is="(item.to && 'Link') || 'button'" :class="resolveItemClass({ active, disabled })" @click="onItemClick(item)">
<slot :name="item.slot" :item="item">
<Icon v-if="item.icon" :name="item.icon" :class="itemIconClass" />
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" @mouseover="onMouseOver">
<transition appear v-bind="ui.transition">
<MenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background]" static>
<div v-for="(subItems, index) of items" :key="index" :class="ui.spacing">
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
<Component
v-bind="omit(item, ['click'])"
:is="(item.to && NuxtLink) || (item.click && 'button') || 'div'"
:class="resolveItemClass({ active, disabled: itemDisabled })"
@click="item.click"
>
<slot :name="item.slot || 'item'" :item="item">
<Icon v-if="item.icon" :name="item.icon" :class="[ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive, item.iconClass]" />
<Avatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
{{ item.label }}
<span class="truncate">{{ item.label }}</span>
<span v-if="item.shortcuts?.length" :class="ui.item.shortcuts">
<kbd v-for="shortcut of item.shortcuts" :key="shortcut" class="font-sans">{{ shortcut }}</kbd>
</span>
</slot>
</Component>
</MenuItem>
@@ -34,128 +45,167 @@
</Menu>
</template>
<script>
import {
Menu,
MenuButton,
MenuItems,
MenuItem
} from '@headlessui/vue'
<script lang="ts">
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import type { PropType } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import { defineComponent, ref, computed, onMounted } from 'vue'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import { classNames, omit } from '../../utils'
import { usePopper } from '../../composables/usePopper'
import type { Avatar as AvatarType } from '../../types/avatar'
import type { PopperOptions } from '../../types'
import { NuxtLink } from '#components'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
import Icon from '../elements/Icon'
import Link from '../elements/Link'
import { classNames, usePopper } from '../../utils'
// const appConfig = useAppConfig()
export default {
export default defineComponent({
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Menu,
MenuButton,
MenuItems,
MenuItem,
Icon,
Link
Avatar
},
props: {
items: {
type: Array,
type: Array as PropType<{
to?: RouteLocationNormalized
exact?: boolean
label: string
disabled?: boolean
slot?: string
icon?: string
iconClass?: string
avatar?: Partial<AvatarType>
click?: Function
shortcuts?: string[]
}[][]>,
default: () => []
},
placement: {
mode: {
type: String,
default: 'bottom-end',
validator: (value) => {
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
default: 'click',
validator: (value: string) => {
return ['click', 'hover'].includes(value)
}
},
strategy: {
type: String,
default: 'fixed',
validator: (value) => {
return ['absolute', 'fixed'].includes(value)
}
disabled: {
type: Boolean,
default: false
},
wrapperClass: {
type: String,
default: 'relative inline-block text-left'
popper: {
type: Object as PropType<PopperOptions>,
default: () => ({})
},
containerClass: {
type: String,
default: 'w-48 z-20'
openDelay: {
type: Number,
default: 50
},
itemsClass: {
type: String,
default: 'u-bg-white divide-y u-divide-gray-100 rounded-md ring-1 ring-black ring-opacity-5'
closeDelay: {
type: Number,
default: 0
},
itemClass: {
type: String,
default: 'group flex items-center px-4 py-2 text-sm w-full'
},
itemActiveClass: {
type: String,
default: 'u-bg-gray-100 u-text-gray-900'
},
itemInactiveClass: {
type: String,
default: 'u-text-gray-700'
},
itemDisabledClass: {
type: String,
default: 'cursor-not-allowed opacity-50'
},
itemIconClass: {
type: String,
default: 'mr-3 h-5 w-5 u-text-gray-400 group-hover:u-text-gray-500'
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.dropdown>>,
default: () => appConfig.ui.dropdown
}
},
setup (props) {
const [trigger, container] = usePopper({
placement: props.placement,
strategy: props.strategy,
modifiers: [{
name: 'offset',
options: {
offset: [0, 8]
}
},
{
name: 'computeStyles',
options: {
gpuAcceleration: false,
adaptive: false
}
},
{
name: 'preventOverflow',
options: {
padding: 8
}
}]
})
// TODO: Remove
const appConfig = useAppConfig()
function resolveItemClass ({ active, disabled }) {
const ui = computed<Partial<typeof appConfig.ui.dropdown>>(() => defu({}, props.ui, appConfig.ui.dropdown))
const popper = computed<PopperOptions>(() => defu({}, props.popper, ui.value.popper as PopperOptions))
const [trigger, container] = usePopper(popper.value)
function resolveItemClass ({ active, disabled }: { active: boolean, disabled: boolean }) {
return classNames(
props.itemClass,
active ? props.itemActiveClass : props.itemInactiveClass,
disabled && props.itemDisabledClass
ui.value.item.base,
active ? ui.value.item.active : ui.value.item.inactive,
disabled && ui.value.item.disabled
)
}
function onItemClick (item) {
if (item.disabled) {
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/menu/menu.ts#L131
const menuApi = ref<any>(null)
let openTimeout: NodeJS.Timeout | null = null
let closeTimeout: NodeJS.Timeout | null = null
onMounted(() => {
setTimeout(() => {
// @ts-expect-error internals
const menuProvides = trigger.value?.$.provides
if (!menuProvides) {
return
}
const menuProvidesSymbols = Object.getOwnPropertySymbols(menuProvides)
menuApi.value = menuProvidesSymbols.length && menuProvides[menuProvidesSymbols[0]]
}, 200)
})
function onMouseOver () {
if (props.mode !== 'hover' || !menuApi.value) {
return
}
if (item.click) {
item.click()
// cancel programmed closing
if (closeTimeout) {
clearTimeout(closeTimeout)
closeTimeout = null
}
// dropdown already open
if (menuApi.value.menuState === 0) {
return
}
openTimeout = openTimeout || setTimeout(() => {
menuApi.value.openMenu && menuApi.value.openMenu()
openTimeout = null
}, props.openDelay)
}
function onMouseLeave () {
if (props.mode !== 'hover' || !menuApi.value) {
return
}
// cancel programmed opening
if (openTimeout) {
clearTimeout(openTimeout)
openTimeout = null
}
// dropdown already closed
if (menuApi.value.menuState === 1) {
return
}
closeTimeout = closeTimeout || setTimeout(() => {
menuApi.value.closeMenu && menuApi.value.closeMenu()
closeTimeout = null
}, props.closeDelay)
}
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
trigger,
container,
onItemClick,
resolveItemClass
resolveItemClass,
onMouseOver,
onMouseLeave,
omit,
NuxtLink
}
}
}
})
</script>

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