@nuxt/ui 3.0.0-alpha.3 → 3.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,93 +1,72 @@
1
1
  [![nuxt-ui.png](https://repository-images.githubusercontent.com/428329515/43fec891-9030-4601-8233-5d45ba5c6013)](https://ui.nuxt.com)
2
2
 
3
- # Nuxt UI
3
+ # Nuxt UI v3
4
4
 
5
5
  [![npm version][npm-version-src]][npm-version-href]
6
6
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
7
7
  [![License][license-src]][license-href]
8
8
  [![Nuxt][nuxt-src]][nuxt-href]
9
9
 
10
- Nuxt UI is a module that provides a set of Vue components and composables built with [Tailwind CSS](https://tailwindcss.com/) and [Headless UI](https://headlessui.dev/) to help you build beautiful and accessible user interfaces.
10
+ We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Radix Vue](https://www.radix-vue.com/), [Tailwind CSS v4](https://tailwindcss.com/blog/tailwindcss-v4-alpha), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
11
11
 
12
- Its goal is to provide everything related to UI when building a Nuxt app. This includes components, icons, colors, dark mode but also keyboard shortcuts.
12
+ ## Installation
13
13
 
14
- ## Features
14
+ 1. Install the Nuxt UI v3 alpha package:
15
15
 
16
- - Built with [Headless UI](https://headlessui.dev/) and [Tailwind CSS](https://tailwindcss.com/)
17
- - HMR support through Nuxt App Config
18
- - Dark mode support
19
- - Support for LTR and RTL languages
20
- - Keyboard shortcuts
21
- - Bundled icons
22
- - Fully typed
23
- - [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
16
+ ```bash [pnpm]
17
+ pnpm add @nuxt/ui@next
18
+ ```
24
19
 
25
- Read more on [ui.nuxt.com](https://ui.nuxt.com)
20
+ ```bash [yarn]
21
+ yarn add @nuxt/ui@next
22
+ ```
26
23
 
27
- ## Installation
24
+ ```bash [npm]
25
+ npm install @nuxt/ui@next
26
+ ```
28
27
 
29
- ```bash
30
- # npm
31
- npm install @nuxt/ui
32
- # yarn
33
- yarn add @nuxt/ui
34
- # pnpm
35
- pnpm add @nuxt/ui
36
- # bun
37
- bun add @nuxt/ui
28
+ ```bash [bun]
29
+ bun add @nuxt/ui@next
38
30
  ```
39
31
 
40
- Then, register the module in your `nuxt.config.ts`:
32
+ > [!WARNING]
33
+ > Make sure you have `typescript` installed in your dev dependencies.
34
+
35
+ 2. Register the Nuxt UI module in your `nuxt.config.ts`:
41
36
 
42
- ```js
37
+ ```ts [nuxt.config.ts]
43
38
  export default defineNuxtConfig({
44
- modules: [
45
- '@nuxt/ui'
46
- ]
39
+ modules: ['@nuxt/ui']
47
40
  })
48
41
  ```
49
42
 
50
- If you want latest updates, please use `@nuxt/ui-edge` in your `package.json`:
43
+ 3. Import Tailwind and Nuxt UI in your `app.vue` or in your [CSS](https://nuxt.com/docs/getting-started/styling#the-css-property):
51
44
 
52
- ```json
53
- {
54
- "devDependencies": {
55
- "@nuxt/ui": "npm:@nuxt/ui-edge@latest"
56
- }
57
- }
45
+ ```css [main.css]
46
+ @import "tailwindcss";
47
+ @import "@nuxt/ui";
58
48
  ```
59
49
 
60
50
  ## Documentation
61
51
 
62
- Visit https://ui.nuxt.com to explore the documentation.
52
+ Visit https://ui3.nuxt.com to explore the documentation.
63
53
 
64
54
  ## Credits
65
55
 
66
56
  - [nuxt/nuxt](https://github.com/nuxt/nuxt)
57
+ - [nuxt/icon](https://github.com/nuxt/icon)
58
+ - [nuxt/fonts](https://github.com/nuxt/fonts)
67
59
  - [nuxt-modules/color-mode](https://github.com/nuxt-modules/color-mode)
68
- - [nuxt-modules/tailwindcss](https://github.com/nuxt-modules/tailwindcss)
60
+ - [radix-vue/radix-vue](https://github.com/radix-vue/radix-vue)
69
61
  - [tailwindlabs/tailwindcss](https://github.com/tailwindlabs/tailwindcss)
70
- - [tailwindlabs/headlessui](https://github.com/tailwindlabs/headlessui)
71
62
  - [vueuse/vueuse](https://github.com/vueuse/vueuse)
72
- - [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons)
73
-
74
- ## Contributing
75
-
76
- Thank you for considering contributing to Nuxt UI. Here are a few ways you can get involved:
77
-
78
- - Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report.
79
- - Suggestions: Have any thoughts to enhance Nuxt UI? We'd love to hear them! Check out the [contribution guide](https://ui.nuxt.com/getting-started/contributing) to share your suggestions.
80
-
81
- ## Local Development
82
-
83
- Follow the docs to [Set up your local development environment](https://ui.nuxt.com/getting-started/contributing#_2-local-development-setup) and contribute.
84
63
 
85
64
  ## License
86
65
 
87
66
  Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/LICENSE.md).
88
67
 
89
68
  <!-- Badges -->
90
- [npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
69
+ [npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/next.svg?style=flat&colorA=18181B&colorB=28CF8D
91
70
  [npm-version-href]: https://npmjs.com/package/@nuxt/ui
92
71
 
93
72
  [npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
@@ -75,7 +75,7 @@ export default defineCommand({
75
75
  await sortFile(themePath)
76
76
 
77
77
  if (!args.prose) {
78
- const typesPath = resolve(path, 'src/runtime/types/index.d.ts')
78
+ const typesPath = resolve(path, 'src/runtime/types/index.ts')
79
79
  await appendFile(typesPath, `export * from '../components/${args.content ? 'content/' : ''}${splitByCase(name).map(p => upperFirst(p)).join('')}.vue'`)
80
80
  await sortFile(typesPath)
81
81
  }
package/cli/templates.mjs CHANGED
@@ -5,7 +5,7 @@ const playground = ({ name, pro }) => {
5
5
  const kebabName = kebabCase(name)
6
6
 
7
7
  return {
8
- filename: `playground/pages/components/${kebabName}.vue`,
8
+ filename: `playground/app/pages/components/${kebabName}.vue`,
9
9
  contents: pro
10
10
  ? undefined
11
11
  : `
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.13.1"
6
6
  },
7
- "version": "3.0.0-alpha.3",
7
+ "version": "3.0.0-alpha.4",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defuFn, defu } from 'defu';
2
2
  import { addTemplate, addTypeTemplate, defineNuxtModule, createResolver, addVitePlugin, hasNuxtModule, installModule, addPlugin, addComponentsDir, addImportsDir } from '@nuxt/kit';
3
- import { fileURLToPath } from 'node:url';
3
+ import 'node:url';
4
4
  import { kebabCase } from 'scule';
5
5
 
6
6
  const accordion = {
@@ -8,7 +8,7 @@ const accordion = {
8
8
  root: "w-full",
9
9
  item: "border-b border-gray-200 dark:border-gray-800 last:border-b-0",
10
10
  header: "flex",
11
- trigger: "group flex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 focus-visible:outline-primary-500 dark:focus-visible:outline-primary-400",
11
+ trigger: "group flex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 focus-visible:outline-primary-500 dark:focus-visible:outline-primary-400 min-w-0",
12
12
  content: "data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] overflow-hidden focus:outline-none",
13
13
  body: "text-sm pb-3.5",
14
14
  leadingIcon: "shrink-0 size-5",
@@ -638,7 +638,7 @@ const collapsible = {
638
638
 
639
639
  const commandPalette = (options) => ({
640
640
  slots: {
641
- root: "flex flex-col min-h-0 divide-y divide-gray-200 dark:divide-gray-800",
641
+ root: "flex flex-col min-h-0 min-w-0 divide-y divide-gray-200 dark:divide-gray-800",
642
642
  input: "[&>input]:h-12",
643
643
  close: "",
644
644
  content: "relative overflow-hidden",
@@ -752,15 +752,48 @@ const contextMenu = (options) => ({
752
752
  const drawer = {
753
753
  slots: {
754
754
  overlay: "fixed inset-0 z-50 bg-gray-200/75 dark:bg-gray-800/75",
755
- content: "fixed inset-x-0 bottom-0 z-50 mt-24 bg-white dark:bg-gray-900 ring ring-gray-200 dark:ring-gray-800 rounded-t-lg h-auto max-h-[96%] flex flex-col focus:outline-none",
756
- handle: "mx-auto w-12 my-4 h-1.5 shrink-0 rounded-full bg-gray-200 dark:bg-gray-700",
757
- container: "mx-auto w-full max-w-md flex flex-col gap-4 px-4 pb-8 overflow-y-auto",
755
+ content: "fixed z-50 bg-white dark:bg-gray-900 ring ring-gray-200 dark:ring-gray-800 flex focus:outline-none",
756
+ handle: "shrink-0 rounded-full bg-gray-200 dark:bg-gray-700",
757
+ container: "w-full flex flex-col gap-4 p-4 overflow-y-auto",
758
758
  header: "",
759
759
  title: "text-gray-900 dark:text-white font-semibold",
760
760
  description: "mt-1 text-gray-500 dark:text-gray-400 text-sm",
761
761
  body: "flex-1",
762
762
  footer: "flex flex-col gap-1.5"
763
- }
763
+ },
764
+ variants: {
765
+ direction: {
766
+ top: {
767
+ content: "top-0 mb-24 flex-col-reverse rounded-b-lg",
768
+ handle: "mb-4"
769
+ },
770
+ right: {
771
+ content: "right-4 flex-row",
772
+ handle: "ml-4"
773
+ },
774
+ bottom: {
775
+ content: "bottom-0 mt-24 flex-col rounded-t-lg",
776
+ handle: "mt-4"
777
+ },
778
+ left: {
779
+ content: "left-4 flex-row-reverse",
780
+ handle: "mr-4"
781
+ }
782
+ }
783
+ },
784
+ compoundVariants: [{
785
+ direction: ["top", "bottom"],
786
+ class: {
787
+ content: "inset-x-0 h-auto max-h-[96%]",
788
+ handle: "w-12 h-1.5 mx-auto"
789
+ }
790
+ }, {
791
+ direction: ["right", "left"],
792
+ class: {
793
+ content: "inset-y-4 w-auto max-w-[calc(100%-2rem)] rounded-lg after:hidden",
794
+ handle: "h-12 w-1.5 mt-auto mb-auto"
795
+ }
796
+ }]
764
797
  };
765
798
 
766
799
  const dropdownMenu = (options) => ({
@@ -1037,7 +1070,7 @@ const inputMenu = (options) => {
1037
1070
  return defuFn({
1038
1071
  slots: {
1039
1072
  base: () => ["rounded-md", options.theme.transitions && "transition-colors"],
1040
- trailing: "absolute inset-y-0 end-0 flex items-center disabled:cursor-not-allowed disabled:opacity-75",
1073
+ trailing: "group absolute inset-y-0 end-0 flex items-center disabled:cursor-not-allowed disabled:opacity-75",
1041
1074
  arrow: "fill-gray-200 dark:fill-gray-800",
1042
1075
  content: "max-h-60 w-[--radix-popper-anchor-width] bg-white dark:bg-gray-900 shadow-lg rounded-md ring ring-gray-200 dark:ring-gray-800 overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]",
1043
1076
  viewport: "divide-y divide-gray-200 dark:divide-gray-800 scroll-py-1",
@@ -2313,7 +2346,7 @@ const textarea = (options) => {
2313
2346
 
2314
2347
  const toast = (options) => ({
2315
2348
  slots: {
2316
- root: "relative overflow-hidden bg-white dark:bg-gray-900 shadow-lg rounded-lg ring ring-gray-200 dark:ring-gray-800 p-4 flex gap-2.5",
2349
+ root: "relative group overflow-hidden bg-white dark:bg-gray-900 shadow-lg rounded-lg ring ring-gray-200 dark:ring-gray-800 p-4 flex gap-2.5 focus:outline-none",
2317
2350
  wrapper: "w-0 flex-1 flex flex-col gap-1",
2318
2351
  title: "text-sm font-medium text-gray-900 dark:text-white",
2319
2352
  description: "text-sm text-gray-500 dark:text-gray-400",
@@ -2327,10 +2360,12 @@ const toast = (options) => ({
2327
2360
  variants: {
2328
2361
  color: {
2329
2362
  ...Object.fromEntries((options.theme.colors || []).map((color) => [color, {
2363
+ root: `focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-${color}-500 dark:focus-visible:ring-${color}-400`,
2330
2364
  icon: `text-${color}-500 dark:text-${color}-400`,
2331
2365
  progress: `bg-${color}-500 dark:bg-${color}-400`
2332
2366
  }])),
2333
2367
  gray: {
2368
+ root: "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-gray-900 dark:focus-visible:ring-white",
2334
2369
  icon: "text-gray-900 dark:text-white",
2335
2370
  progress: "bg-gray-900 dark:bg-white"
2336
2371
  }
@@ -2353,7 +2388,7 @@ const toast = (options) => ({
2353
2388
 
2354
2389
  const toaster = {
2355
2390
  slots: {
2356
- viewport: "fixed flex flex-col w-[calc(100%-2rem)] sm:w-96 z-[100] data-[expanded=true]:h-[--height]",
2391
+ viewport: "fixed flex flex-col w-[calc(100%-2rem)] sm:w-96 z-[100] data-[expanded=true]:h-[--height] focus:outline-none",
2357
2392
  base: "absolute inset-x-0 z-[--index] transform-[--transform] data-[expanded=false]:data-[front=false]:h-[--front-height] data-[expanded=false]:data-[front=false]:*:invisible data-[state=closed]:animate-[toast-closed_200ms_ease-in-out] data-[state=closed]:data-[expanded=false]:data-[front=false]:animate-[toast-collapsed-closed_200ms_ease-in-out] data-[swipe=move]:transition-none transition-[transform,translate,height] duration-200 ease-out"
2358
2393
  },
2359
2394
  variants: {
@@ -2480,14 +2515,6 @@ function addTemplates(options, nuxt) {
2480
2515
  return `${before}${replaced}${after}`;
2481
2516
  });
2482
2517
  }
2483
- if (process.env.DEV) {
2484
- return [
2485
- `import template from ${JSON.stringify(fileURLToPath(new URL(`./theme/${kebabCase(component)}`, import.meta.url)))}`,
2486
- `const result = typeof template === 'function' ? template(${JSON.stringify(options)}) : template`,
2487
- `const json = ${json}`,
2488
- `export default result as typeof json`
2489
- ].join("\n");
2490
- }
2491
2518
  return `export default ${json}`;
2492
2519
  }
2493
2520
  });
@@ -2502,13 +2529,13 @@ function addTemplates(options, nuxt) {
2502
2529
  getContents: () => `import * as ui from '#build/ui'
2503
2530
  import type { DeepPartial } from '#ui/types/utils'
2504
2531
 
2505
- const colors = ${JSON.stringify(options.colors)} as const;
2532
+ const colors = ${JSON.stringify(options.theme?.colors || [])} as const;
2506
2533
  const icons = ${JSON.stringify(nuxt.options.appConfig.ui.icons)};
2507
2534
 
2508
2535
  type AppConfigUI = {
2509
2536
  colors?: {
2510
- primary?: Exclude<typeof colors[number], 'error'>
2511
- error?: Exclude<typeof colors[number], 'primary'>
2537
+ primary?: Exclude<typeof colors[number], 'error' | 'primary'>
2538
+ error?: Exclude<typeof colors[number], 'primary' | 'error'>
2512
2539
  gray?: 'slate' | 'cool' | 'zinc' | 'neutral' | 'stone'
2513
2540
  }
2514
2541
  icons?: Partial<typeof icons>
@@ -2566,7 +2593,7 @@ const module = defineNuxtModule({
2566
2593
  colors: {
2567
2594
  primary: "green",
2568
2595
  error: "red",
2569
- gray: "cool"
2596
+ gray: "slate"
2570
2597
  },
2571
2598
  icons
2572
2599
  });
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv } from 'tailwind-variants'
3
4
  import type { AccordionRootProps, AccordionRootEmits, AccordionItemProps } from 'radix-vue'
@@ -47,6 +48,7 @@ export type AccordionSlots<T extends { slot?: string }> = {
47
48
  content: SlotProps<T>
48
49
  body: SlotProps<T>
49
50
  } & DynamicSlots<T, SlotProps<T>>
51
+
50
52
  </script>
51
53
 
52
54
  <script setup lang="ts" generic="T extends AccordionItem">
@@ -46,7 +46,7 @@ export interface AlertProps {
46
46
  }
47
47
 
48
48
  export interface AlertEmits {
49
- (e: 'close'): void
49
+ (e: 'update:open', value: boolean): void
50
50
  }
51
51
 
52
52
  export interface AlertSlots {
@@ -122,7 +122,7 @@ const ui = computed(() => alert({
122
122
  aria-label="Close"
123
123
  v-bind="typeof close === 'object' ? close : undefined"
124
124
  :class="ui.close({ class: props.ui?.close })"
125
- @click="emits('close')"
125
+ @click="emits('update:open', false)"
126
126
  />
127
127
  </slot>
128
128
  </div>
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv } from 'tailwind-variants'
3
4
  import type { AppConfig } from '@nuxt/schema'
@@ -42,6 +43,7 @@ export type BreadcrumbSlots<T extends { slot?: string }> = {
42
43
  'item-trailing': SlotProps<T>
43
44
  'separator'(props?: {}): any
44
45
  } & DynamicSlots<T, SlotProps<T>>
46
+
45
47
  </script>
46
48
 
47
49
  <script setup lang="ts" generic="T extends BreadcrumbItem">
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv } from 'tailwind-variants'
3
4
  import type { ComboboxRootProps, ComboboxRootEmits, ComboboxItemProps } from 'radix-vue'
@@ -65,7 +66,7 @@ export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'mult
65
66
  */
66
67
  placeholder?: InputProps['placeholder']
67
68
  /**
68
- * Display a close button in the input (useful when inside a `UModal`).
69
+ * Display a close button in the input (useful when inside a Modal for example).
69
70
  * `{ size: 'md', color: 'gray', variant: 'ghost' }`{lang="ts-type"}
70
71
  * @defaultValue false
71
72
  */
@@ -78,7 +79,15 @@ export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'mult
78
79
  groups?: G[]
79
80
  /**
80
81
  * Options for [useFuse](https://vueuse.org/integrations/useFuse).
81
- * @defaultValue { ignoreLocation: true, threshold: 0.1, keys: ['label', 'suffix'], resultLimit: 12, matchAllWhenSearchEmpty: true }
82
+ * @defaultValue {
83
+ fuseOptions: {
84
+ ignoreLocation: true,
85
+ threshold: 0.1,
86
+ keys: ['label', 'suffix']
87
+ },
88
+ resultLimit: 12,
89
+ matchAllWhenSearchEmpty: true
90
+ }
82
91
  */
83
92
  fuse?: UseFuseOptions<T>
84
93
  class?: any
@@ -97,6 +106,7 @@ export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?:
97
106
  'item-label': SlotProps<T>
98
107
  'item-trailing': SlotProps<T>
99
108
  } & DynamicSlots<G, SlotProps<T>> & DynamicSlots<T, SlotProps<T>>
109
+
100
110
  </script>
101
111
 
102
112
  <script setup lang="ts" generic="G extends CommandPaletteGroup<T>, T extends CommandPaletteItem">
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv, type VariantProps } from 'tailwind-variants'
3
4
  import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps, ContextMenuTriggerProps, ContextMenuItemProps } from 'radix-vue'
@@ -56,6 +57,7 @@ export type ContextMenuSlots<T extends { slot?: string }> = {
56
57
  'item-label': SlotProps<T>
57
58
  'item-trailing': SlotProps<T>
58
59
  } & DynamicSlots<T, SlotProps<T>>
60
+
59
61
  </script>
60
62
 
61
63
  <script setup lang="ts" generic="T extends ContextMenuItem">
@@ -10,7 +10,7 @@ const appConfig = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme
10
10
 
11
11
  const drawer = tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}) })
12
12
 
13
- export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'defaultOpen' | 'direction' | 'dismissible' | 'fadeFromIndex' | 'fixed' | 'modal' | 'nested' | 'open' | 'scrollLockTimeout' | 'shouldScaleBackground' | 'snapPoints'> {
13
+ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'defaultOpen' | 'direction' | 'dismissible' | 'fadeFromIndex' | 'fixed' | 'modal' | 'nested' | 'direction' | 'open' | 'scrollLockTimeout' | 'shouldScaleBackground' | 'snapPoints'> {
14
14
  /**
15
15
  * The element or component this component should render as.
16
16
  * @defaultValue 'div'
@@ -25,6 +25,11 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
25
25
  * @defaultValue true
26
26
  */
27
27
  overlay?: boolean
28
+ /**
29
+ * Render a handle on the drawer.
30
+ * @defaultValue true
31
+ */
32
+ handle?: boolean
28
33
  /**
29
34
  * Render the drawer in a portal.
30
35
  * @defaultValue true
@@ -49,23 +54,26 @@ export interface DrawerSlots {
49
54
  </script>
50
55
 
51
56
  <script setup lang="ts">
52
- import { toRef } from 'vue'
57
+ import { computed, toRef } from 'vue'
53
58
  import { useForwardPropsEmits } from 'radix-vue'
54
59
  import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription } from 'vaul-vue'
55
60
  import { reactivePick } from '@vueuse/core'
56
61
 
57
62
  const props = withDefaults(defineProps<DrawerProps>(), {
63
+ direction: 'bottom',
58
64
  portal: true,
59
- overlay: true
65
+ overlay: true,
66
+ handle: true
60
67
  })
61
68
  const emits = defineEmits<DrawerEmits>()
62
69
  const slots = defineSlots<DrawerSlots>()
63
70
 
64
- const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'defaultOpen', 'dismissible', 'fadeFromIndex', 'fixed', 'modal', 'nested', 'open', 'scrollLockTimeout', 'shouldScaleBackground', 'snapPoints'), emits)
71
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'defaultOpen', 'dismissible', 'fadeFromIndex', 'fixed', 'modal', 'nested', 'direction', 'open', 'scrollLockTimeout', 'shouldScaleBackground', 'snapPoints'), emits)
65
72
  const contentProps = toRef(() => props.content)
66
73
 
67
- // eslint-disable-next-line vue/no-dupe-keys
68
- const ui = drawer()
74
+ const ui = computed(() => drawer({
75
+ direction: props.direction
76
+ }))
69
77
  </script>
70
78
 
71
79
  <template>
@@ -79,7 +87,7 @@ const ui = drawer()
79
87
 
80
88
  <DrawerContent :class="ui.content({ class: [props.class, props.ui?.content] })" v-bind="contentProps">
81
89
  <slot name="handle">
82
- <div :class="ui.handle({ class: props.ui?.handle })" />
90
+ <div v-if="handle" :class="ui.handle({ class: props.ui?.handle })" />
83
91
  </slot>
84
92
 
85
93
  <slot name="content">
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv, type VariantProps } from 'tailwind-variants'
3
4
  import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps, DropdownMenuTriggerProps, DropdownMenuItemProps } from 'radix-vue'
@@ -64,6 +65,7 @@ export type DropdownMenuSlots<T extends { slot?: string }> = {
64
65
  'item-label': SlotProps<T>
65
66
  'item-trailing': SlotProps<T>
66
67
  } & DynamicSlots<T, SlotProps<T>>
68
+
67
69
  </script>
68
70
 
69
71
  <script setup lang="ts" generic="T extends DropdownMenuItem">
@@ -23,6 +23,7 @@ export interface InputMenuItem extends Pick<ComboboxItemProps, 'disabled'> {
23
23
  * @defaultValue 'item'
24
24
  */
25
25
  type?: 'label' | 'separator' | 'item'
26
+ select?(e?: Event): void
26
27
  }
27
28
 
28
29
  type InputMenuVariants = VariantProps<typeof inputMenu>
@@ -100,7 +101,7 @@ export type InputMenuEmits<T> = ComboboxRootEmits<T> & {
100
101
 
101
102
  type SlotProps<T> = (props: { item: T, index: number }) => any
102
103
 
103
- export type InputMenuSlots<T> = {
104
+ export interface InputMenuSlots<T> {
104
105
  'leading'(props: { modelValue: T, open: boolean, ui: any }): any
105
106
  'trailing'(props: { modelValue: T, open: boolean, ui: any }): any
106
107
  'empty'(props: { searchTerm?: string }): any
@@ -332,6 +333,7 @@ defineExpose({
332
333
  :class="ui.item({ class: props.ui?.item })"
333
334
  :disabled="item.disabled"
334
335
  :value="valueKey && typeof item === 'object' ? (item[valueKey as keyof InputMenuItem]) as AcceptableValue : item"
336
+ @select="item.select"
335
337
  >
336
338
  <slot name="item" :item="(item as T)" :index="index">
337
339
  <slot name="item-leading" :item="(item as T)" :index="index">
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv, type VariantProps } from 'tailwind-variants'
3
4
  import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuItemProps, NavigationMenuContentProps } from 'radix-vue'
@@ -74,6 +75,7 @@ export type NavigationMenuSlots<T extends { slot?: string }> = {
74
75
  'item-label': SlotProps<T>
75
76
  'item-trailing': SlotProps<T>
76
77
  } & DynamicSlots<T, SlotProps<T>>
78
+
77
79
  </script>
78
80
 
79
81
  <script setup lang="ts" generic="T extends NavigationMenuItem">
@@ -44,17 +44,17 @@ export interface PaginationProps extends Pick<PaginationRootProps, 'defaultPage'
44
44
  ellipsisIcon?: string
45
45
  /**
46
46
  * The color of the pagination controls.
47
- * @defaultValue 'white'
47
+ * @defaultValue 'gray'
48
48
  */
49
49
  color?: ButtonProps['color']
50
50
  /**
51
51
  * The variant of the pagination controls.
52
- * @defaultValue 'solid'
52
+ * @defaultValue 'outline'
53
53
  */
54
54
  variant?: ButtonProps['variant']
55
55
  /**
56
56
  * The color of the active pagination control.
57
- * @defaultValue 'black'
57
+ * @defaultValue 'primary'
58
58
  */
59
59
  activeColor?: ButtonProps['color']
60
60
  /**
@@ -105,11 +105,16 @@ import { reactivePick } from '@vueuse/core'
105
105
  import { useAppConfig } from '#imports'
106
106
 
107
107
  const props = withDefaults(defineProps<PaginationProps>(), {
108
+ size: 'md',
108
109
  color: 'gray',
109
- activeColor: 'primary',
110
110
  variant: 'outline',
111
+ activeColor: 'primary',
111
112
  activeVariant: 'solid',
112
- showControls: true
113
+ showControls: true,
114
+ showEdges: false,
115
+ itemsPerPage: 10,
116
+ siblingCount: 2,
117
+ total: 0
113
118
  })
114
119
  const emits = defineEmits<PaginationEmits>()
115
120
  const slots = defineSlots<PaginationSlots>()
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv, type VariantProps } from 'tailwind-variants'
3
4
  import type { ProgressRootProps, ProgressRootEmits } from 'radix-vue'
@@ -42,6 +43,7 @@ export type ProgressSlots = {
42
43
  } & {
43
44
  [key: string]: (props: { step: number }) => any
44
45
  }
46
+
45
47
  </script>
46
48
 
47
49
  <script setup lang="ts">
@@ -22,6 +22,7 @@ export interface SelectMenuItem extends Pick<ComboboxItemProps, 'disabled'> {
22
22
  * @defaultValue 'item'
23
23
  */
24
24
  type?: 'label' | 'separator' | 'item'
25
+ select?(e?: Event): void
25
26
  }
26
27
 
27
28
  type SelectMenuVariants = VariantProps<typeof selectMenu>
@@ -32,11 +33,10 @@ export interface SelectMenuProps<T> extends Pick<ComboboxRootProps<T>, 'modelVal
32
33
  placeholder?: string
33
34
  /**
34
35
  * Wether to display the search input or not.
35
- * @defaultValue true
36
+ * Can be an object to pass additional props to the input.
37
+ * @defaultValue { placeholder: 'Search...' }
36
38
  */
37
- search?: boolean
38
- /** The placeholder text when the search input is empty. */
39
- searchPlaceholder?: string
39
+ searchInput?: boolean | { placeholder?: string }
40
40
  color?: SelectMenuVariants['color']
41
41
  variant?: SelectMenuVariants['variant']
42
42
  size?: SelectMenuVariants['size']
@@ -92,7 +92,7 @@ export type SelectMenuEmits<T> = ComboboxRootEmits<T> & {
92
92
 
93
93
  type SlotProps<T> = (props: { item: T, index: number }) => any
94
94
 
95
- export type SelectMenuSlots<T> = {
95
+ export interface SelectMenuSlots<T> {
96
96
  'leading'(props: { modelValue: T, open: boolean, ui: any }): any
97
97
  'default'(props: { modelValue: T, open: boolean }): any
98
98
  'trailing'(props: { modelValue: T, open: boolean, ui: any }): any
@@ -122,7 +122,7 @@ const props = withDefaults(defineProps<SelectMenuProps<T>>(), {
122
122
  search: true,
123
123
  portal: true,
124
124
  autofocusDelay: 0,
125
- searchPlaceholder: 'Search...',
125
+ searchInput: () => ({ placeholder: 'Search...' }),
126
126
  filter: () => ['label']
127
127
  })
128
128
  const emits = defineEmits<SelectMenuEmits<T>>()
@@ -245,7 +245,13 @@ function onUpdateOpen(value: boolean) {
245
245
 
246
246
  <ComboboxPortal :disabled="!portal">
247
247
  <ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
248
- <ComboboxInput v-if="search" :placeholder="searchPlaceholder" :class="ui.input({ class: props.ui?.input })" autofocus autocomplete="off" />
248
+ <ComboboxInput
249
+ v-if="!!searchInput"
250
+ autofocus
251
+ autocomplete="off"
252
+ v-bind="typeof searchInput === 'object' ? searchInput : {}"
253
+ :class="ui.input({ class: props.ui?.input })"
254
+ />
249
255
 
250
256
  <ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
251
257
  <slot name="empty" :search-term="searchTerm">
@@ -267,6 +273,7 @@ function onUpdateOpen(value: boolean) {
267
273
  :class="ui.item({ class: props.ui?.item })"
268
274
  :disabled="item.disabled"
269
275
  :value="valueKey && typeof item === 'object' ? (item[valueKey as keyof SelectMenuItem]) as AcceptableValue : item"
276
+ @select="item.select"
270
277
  >
271
278
  <slot name="item" :item="(item as T)" :index="index">
272
279
  <slot name="item-leading" :item="(item as T)" :index="index">
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import { tv, type VariantProps } from 'tailwind-variants'
3
4
  import type { TabsRootProps, TabsRootEmits, TabsContentProps, TabsTriggerProps } from 'radix-vue'
@@ -57,6 +58,7 @@ export type TabsSlots<T extends { slot?: string }> = {
57
58
  trailing: SlotProps<T>
58
59
  content: SlotProps<T>
59
60
  } & DynamicSlots<T, SlotProps<T>>
61
+
60
62
  </script>
61
63
 
62
64
  <script setup lang="ts" generic="T extends TabsItem">
@@ -1,5 +1,5 @@
1
1
  import { type ComputedRef, type InjectionKey } from 'vue';
2
- import type { AvatarGroupProps } from '../types/index.js';
2
+ import type { AvatarGroupProps } from '../types';
3
3
  export declare const avatarGroupInjectionKey: InjectionKey<ComputedRef<{
4
4
  size: AvatarGroupProps['size'];
5
5
  }>>;
@@ -1,6 +1,6 @@
1
1
  import type { InjectionKey, ComputedRef } from 'vue';
2
- import type { ButtonGroupProps } from '../components/ButtonGroup.vue.js';
3
- import type { GetObjectField } from '../types/utils.js';
2
+ import type { ButtonGroupProps } from '../components/ButtonGroup.vue';
3
+ import type { GetObjectField } from '../types/utils';
4
4
  export declare const buttonGroupInjectionKey: InjectionKey<ComputedRef<{
5
5
  size: ButtonGroupProps['size'];
6
6
  orientation: ButtonGroupProps['orientation'];
@@ -1,8 +1,8 @@
1
1
  import { type InjectionKey, type Ref, type ComputedRef } from 'vue';
2
2
  import { type UseEventBusReturn } from '@vueuse/core';
3
- import type { FormFieldProps } from '../types/index.js';
4
- import type { FormEvent, FormFieldInjectedOptions, FormInjectedOptions } from '../types/form.js';
5
- import type { GetObjectField } from '../types/utils.js';
3
+ import type { FormFieldProps } from '../types';
4
+ import type { FormEvent, FormFieldInjectedOptions, FormInjectedOptions } from '../types/form';
5
+ import type { GetObjectField } from '../types/utils';
6
6
  type Props<T> = {
7
7
  id?: string;
8
8
  name?: string;
@@ -1,6 +1,6 @@
1
1
  import type { ShallowRef, Component, InjectionKey } from 'vue';
2
- import type { ModalProps } from '../types/index.js';
3
- import type { ComponentProps } from '../types/component.js';
2
+ import type { ModalProps } from '../types';
3
+ import type { ComponentProps } from '../types/component';
4
4
  export interface ModalState {
5
5
  component: Component | string;
6
6
  props: ModalProps;
@@ -1,6 +1,6 @@
1
1
  import type { ShallowRef, Component, InjectionKey } from 'vue';
2
- import type { SlideoverProps } from '../types/index.js';
3
- import type { ComponentProps } from '../types/component.js';
2
+ import type { SlideoverProps } from '../types';
3
+ import type { ComponentProps } from '../types/component';
4
4
  export interface SlideoverState {
5
5
  component: Component | string;
6
6
  props: SlideoverProps;
@@ -1,4 +1,4 @@
1
- import type { ToastProps } from '../types/index.js';
1
+ import type { ToastProps } from '../types';
2
2
  export interface Toast extends Omit<ToastProps, 'defaultOpen'> {
3
3
  id: string | number;
4
4
  click?: (toast: Toast) => void;
@@ -3,7 +3,7 @@ import type { ZodSchema } from 'zod';
3
3
  import type { Schema as JoiSchema } from 'joi';
4
4
  import type { ObjectSchema as YupObjectSchema } from 'yup';
5
5
  import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchemaAsync, SafeParser as ValibotSafeParser, SafeParserAsync as ValibotSafeParserAsync } from 'valibot';
6
- import type { GetObjectField } from './utils.js';
6
+ import type { GetObjectField } from './utils';
7
7
  export interface Form<T> {
8
8
  validate(opts?: {
9
9
  name: string | string[];
@@ -1,42 +1,42 @@
1
- export * from '../components/Accordion.vue.js';
2
- export * from '../components/Alert.vue.js';
3
- export * from '../components/App.vue.js';
4
- export * from '../components/Avatar.vue.js';
5
- export * from '../components/AvatarGroup.vue.js';
6
- export * from '../components/Badge.vue.js';
7
- export * from '../components/Breadcrumb.vue.js';
8
- export * from '../components/Button.vue.js';
9
- export * from '../components/Card.vue.js';
10
- export * from '../components/Checkbox.vue.js';
11
- export * from '../components/Chip.vue.js';
12
- export * from '../components/Collapsible.vue.js';
13
- export * from '../components/CommandPalette.vue.js';
14
- export * from '../components/Container.vue.js';
15
- export * from '../components/ContextMenu.vue.js';
16
- export * from '../components/Drawer.vue.js';
17
- export * from '../components/DropdownMenu.vue.js';
18
- export * from '../components/Form.vue.js';
19
- export * from '../components/FormField.vue.js';
20
- export * from '../components/Input.vue.js';
21
- export * from '../components/InputMenu.vue.js';
22
- export * from '../components/Kbd.vue.js';
23
- export * from '../components/Link.vue.js';
24
- export * from '../components/Modal.vue.js';
25
- export * from '../components/NavigationMenu.vue.js';
26
- export * from '../components/Pagination.vue.js';
27
- export * from '../components/Popover.vue.js';
28
- export * from '../components/Progress.vue.js';
29
- export * from '../components/RadioGroup.vue.js';
30
- export * from '../components/Select.vue.js';
31
- export * from '../components/SelectMenu.vue.js';
32
- export * from '../components/Separator.vue.js';
33
- export * from '../components/Skeleton.vue.js';
34
- export * from '../components/Slideover.vue.js';
35
- export * from '../components/Slider.vue.js';
36
- export * from '../components/Switch.vue.js';
37
- export * from '../components/Tabs.vue.js';
38
- export * from '../components/Textarea.vue.js';
39
- export * from '../components/Toast.vue.js';
40
- export * from '../components/Toaster.vue.js';
41
- export * from '../components/Tooltip.vue.js';
42
- export * from './form.js';
1
+ export * from '../components/Accordion.vue';
2
+ export * from '../components/Alert.vue';
3
+ export * from '../components/App.vue';
4
+ export * from '../components/Avatar.vue';
5
+ export * from '../components/AvatarGroup.vue';
6
+ export * from '../components/Badge.vue';
7
+ export * from '../components/Breadcrumb.vue';
8
+ export * from '../components/Button.vue';
9
+ export * from '../components/Card.vue';
10
+ export * from '../components/Checkbox.vue';
11
+ export * from '../components/Chip.vue';
12
+ export * from '../components/Collapsible.vue';
13
+ export * from '../components/CommandPalette.vue';
14
+ export * from '../components/Container.vue';
15
+ export * from '../components/ContextMenu.vue';
16
+ export * from '../components/Drawer.vue';
17
+ export * from '../components/DropdownMenu.vue';
18
+ export * from '../components/Form.vue';
19
+ export * from '../components/FormField.vue';
20
+ export * from '../components/Input.vue';
21
+ export * from '../components/InputMenu.vue';
22
+ export * from '../components/Kbd.vue';
23
+ export * from '../components/Link.vue';
24
+ export * from '../components/Modal.vue';
25
+ export * from '../components/NavigationMenu.vue';
26
+ export * from '../components/Pagination.vue';
27
+ export * from '../components/Popover.vue';
28
+ export * from '../components/Progress.vue';
29
+ export * from '../components/RadioGroup.vue';
30
+ export * from '../components/Select.vue';
31
+ export * from '../components/SelectMenu.vue';
32
+ export * from '../components/Separator.vue';
33
+ export * from '../components/Skeleton.vue';
34
+ export * from '../components/Slideover.vue';
35
+ export * from '../components/Slider.vue';
36
+ export * from '../components/Switch.vue';
37
+ export * from '../components/Tabs.vue';
38
+ export * from '../components/Textarea.vue';
39
+ export * from '../components/Toast.vue';
40
+ export * from '../components/Toaster.vue';
41
+ export * from '../components/Tooltip.vue';
42
+ export * from './form';
@@ -2,7 +2,7 @@ import type { ZodSchema } from 'zod';
2
2
  import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi';
3
3
  import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup';
4
4
  import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchemaAsync, SafeParser as ValibotSafeParser, SafeParserAsync as ValibotSafeParserAsync } from 'valibot';
5
- import type { FormError } from '../types/form.js';
5
+ import type { FormError } from '../types/form';
6
6
  export declare function isYupSchema(schema: any): schema is YupObjectSchema<any>;
7
7
  export declare function isYupError(error: any): error is YupError;
8
8
  export declare function getYupErrors(state: any, schema: YupObjectSchema<any>): Promise<FormError[]>;
@@ -1,8 +1,10 @@
1
- import type { LinkProps } from '../types/index.js';
1
+ import type { LinkProps } from '../types';
2
2
  export declare function pickLinkProps(link: LinkProps & {
3
3
  ariaLabel?: string;
4
+ title?: string;
4
5
  }): {
5
6
  type: any;
7
+ title: any;
6
8
  active: any;
7
9
  activeClass: any;
8
10
  ariaCurrentValue: any;
@@ -1,4 +1,4 @@
1
1
  import { reactivePick } from "@vueuse/core";
2
2
  export function pickLinkProps(link) {
3
- return reactivePick(link, "active", "activeClass", "ariaCurrentValue", "ariaLabel", "as", "disabled", "exact", "exactActiveClass", "exactHash", "exactQuery", "external", "href", "inactiveClass", "noPrefetch", "noRel", "prefetch", "prefetchedClass", "rel", "replace", "target", "to", "type");
3
+ return reactivePick(link, "active", "activeClass", "ariaCurrentValue", "ariaLabel", "as", "disabled", "exact", "exactActiveClass", "exactHash", "exactQuery", "external", "href", "inactiveClass", "noPrefetch", "noRel", "prefetch", "prefetchedClass", "rel", "replace", "target", "to", "type", "title");
4
4
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@nuxt/ui",
3
3
  "description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
4
- "version": "3.0.0-alpha.3",
5
- "packageManager": "pnpm@9.10.0",
4
+ "version": "3.0.0-alpha.4",
5
+ "packageManager": "pnpm@9.11.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/nuxt/ui.git"
@@ -45,8 +45,8 @@
45
45
  "release": "release-it --preRelease=alpha --npm.tag=next"
46
46
  },
47
47
  "dependencies": {
48
- "@nuxt/fonts": "^0.8.0",
49
- "@nuxt/icon": "^1.5.1",
48
+ "@nuxt/fonts": "^0.9.2",
49
+ "@nuxt/icon": "^1.5.2",
50
50
  "@nuxt/kit": "^3.13.2",
51
51
  "@nuxt/schema": "^3.13.2",
52
52
  "@nuxtjs/color-mode": "^3.5.1",
@@ -57,7 +57,7 @@
57
57
  "defu": "^6.1.4",
58
58
  "fuse.js": "^7.0.0",
59
59
  "ohash": "^1.1.4",
60
- "radix-vue": "^1.9.5",
60
+ "radix-vue": "^1.9.6",
61
61
  "scule": "^1.3.0",
62
62
  "tailwind-variants": "^0.2.1",
63
63
  "tailwindcss": "4.0.0-alpha.24",
@@ -69,12 +69,12 @@
69
69
  "@nuxt/test-utils": "^3.14.2",
70
70
  "@release-it/conventional-changelog": "^8.0.2",
71
71
  "@vue/test-utils": "^2.4.6",
72
- "eslint": "^9.10.0",
72
+ "eslint": "^9.11.1",
73
73
  "happy-dom": "^15.7.4",
74
74
  "joi": "^17.13.3",
75
75
  "nuxt": "^3.13.2",
76
76
  "release-it": "^17.6.0",
77
- "valibot": "^0.42.0",
77
+ "valibot": "^0.42.1",
78
78
  "vitest": "^2.1.1",
79
79
  "vitest-environment-nuxt": "^1.0.1",
80
80
  "vue-tsc": "^2.1.6",