diff --git a/docs/app/assets/icons/embla-carousel.svg b/docs/app/assets/icons/embla-carousel.svg new file mode 100644 index 00000000..9dbcd64f --- /dev/null +++ b/docs/app/assets/icons/embla-carousel.svg @@ -0,0 +1 @@ + diff --git a/docs/app/components/content/examples/carousel/CarouselArrowsExample.vue b/docs/app/components/content/examples/carousel/CarouselArrowsExample.vue new file mode 100644 index 00000000..7f1f258a --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselArrowsExample.vue @@ -0,0 +1,16 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselAutoHeightExample.vue b/docs/app/components/content/examples/carousel/CarouselAutoHeightExample.vue new file mode 100644 index 00000000..a1886866 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselAutoHeightExample.vue @@ -0,0 +1,29 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselAutoScrollExample.vue b/docs/app/components/content/examples/carousel/CarouselAutoScrollExample.vue new file mode 100644 index 00000000..e89b24ba --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselAutoScrollExample.vue @@ -0,0 +1,24 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselAutoplayExample.vue b/docs/app/components/content/examples/carousel/CarouselAutoplayExample.vue new file mode 100644 index 00000000..e7bfa75f --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselAutoplayExample.vue @@ -0,0 +1,24 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselClassNamesExample.vue b/docs/app/components/content/examples/carousel/CarouselClassNamesExample.vue new file mode 100644 index 00000000..889185fa --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselClassNamesExample.vue @@ -0,0 +1,25 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselDotsExample.vue b/docs/app/components/content/examples/carousel/CarouselDotsExample.vue new file mode 100644 index 00000000..9c02afa0 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselDotsExample.vue @@ -0,0 +1,16 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselDotsMultipleExample.vue b/docs/app/components/content/examples/carousel/CarouselDotsMultipleExample.vue new file mode 100644 index 00000000..3037ae17 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselDotsMultipleExample.vue @@ -0,0 +1,16 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselFadeExample.vue b/docs/app/components/content/examples/carousel/CarouselFadeExample.vue new file mode 100644 index 00000000..dc83f9ba --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselFadeExample.vue @@ -0,0 +1,23 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselItemsExample.vue b/docs/app/components/content/examples/carousel/CarouselItemsExample.vue new file mode 100644 index 00000000..0559d053 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselItemsExample.vue @@ -0,0 +1,16 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselItemsMultipleExample.vue b/docs/app/components/content/examples/carousel/CarouselItemsMultipleExample.vue new file mode 100644 index 00000000..6ed826e3 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselItemsMultipleExample.vue @@ -0,0 +1,16 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselOrientationExample.vue b/docs/app/components/content/examples/carousel/CarouselOrientationExample.vue new file mode 100644 index 00000000..38ae34fc --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselOrientationExample.vue @@ -0,0 +1,22 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselPrevNextExample.vue b/docs/app/components/content/examples/carousel/CarouselPrevNextExample.vue new file mode 100644 index 00000000..c248c0f8 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselPrevNextExample.vue @@ -0,0 +1,23 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselPrevNextIconExample.vue b/docs/app/components/content/examples/carousel/CarouselPrevNextIconExample.vue new file mode 100644 index 00000000..6235e5e4 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselPrevNextIconExample.vue @@ -0,0 +1,28 @@ + + + diff --git a/docs/app/components/content/examples/carousel/CarouselWheelGesturesExample.vue b/docs/app/components/content/examples/carousel/CarouselWheelGesturesExample.vue new file mode 100644 index 00000000..0a895634 --- /dev/null +++ b/docs/app/components/content/examples/carousel/CarouselWheelGesturesExample.vue @@ -0,0 +1,22 @@ + + + diff --git a/docs/content/1.getting-started/3.theme.md b/docs/content/1.getting-started/3.theme.md index 2011afb5..b371ed20 100644 --- a/docs/content/1.getting-started/3.theme.md +++ b/docs/content/1.getting-started/3.theme.md @@ -36,7 +36,7 @@ Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your ``` -The `@theme` directive tells Tailwind to make new utilities and variants available based on those variables. It's the equivalent of the `theme.extend` key in Tailwind CSS v3 `tailwind.config.ts` file. +The `@theme` directive tells Tailwind to make new utilities and variants available based on these variables. It's the equivalent of the `theme.extend` key in Tailwind CSS v3 `tailwind.config.ts` file. ::note You can learn more about this on [https://tailwindcss.com/blog/tailwindcss-v4-alpha](https://tailwindcss.com/blog/tailwindcss-v4-alpha#css-first-configuration). @@ -383,7 +383,7 @@ export default { :: -::note +::warning Components without slots don't have a [`ui` prop](#ui-prop), only the [`class` prop](#class-prop) is available to override styles. :: @@ -434,11 +434,11 @@ The `defaultVariants` property specifies the default values for each variant. It You have multiple ways to customize the appearance of Nuxt UI components, you can do it for all components at once or on a per-component basis. -::tip +::note Tailwind Variants uses [tailwind-merge](https://github.com/dcastil/tailwind-merge) under the hood to merge classes so you don't have to worry about conflicting classes. :: -::note +::tip You can explore the theme for each component in two ways: - Check the `Theme` section in the documentation of each individual component. @@ -463,7 +463,9 @@ export default defineAppConfig({ }) ``` +::note In this example, the `font-bold` class will override the default `font-medium` class on all buttons. +:: ### `ui` prop @@ -492,7 +494,9 @@ slots: --- :: +::note In this example, the `trailingIcon` slot is overwritten with `size-3` even though the `md` size variant would apply a `size-5` class to it. +:: ### `class` prop @@ -506,3 +510,7 @@ slots: default: Button --- :: + +::note +In this example, the `font-bold` class will override the default `font-medium` class on this button. +:: diff --git a/docs/content/1.getting-started/4.icons.md b/docs/content/1.getting-started/4.icons.md index 50d686f7..5eb5bb7b 100644 --- a/docs/content/1.getting-started/4.icons.md +++ b/docs/content/1.getting-started/4.icons.md @@ -69,7 +69,7 @@ npm install @iconify-json/{collection_name} For example, to use the `i-uil-github` icon, install it's collection with `@iconify-json/uil`. This way the icons can be served locally or from your serverless functions, which is faster and more reliable on both SSR and client-side. -::tip{to="https://github.com/nuxt/icon?tab=readme-ov-file#iconify-dataset" target="_blank"} +::note{to="https://github.com/nuxt/icon?tab=readme-ov-file#iconify-dataset" target="_blank"} Read more about this in the `@nuxt/icon` documentation. :: @@ -109,7 +109,7 @@ Then you can use the icons like this: ``` -::tip{to="https://github.com/nuxt/icon?tab=readme-ov-file#custom-local-collections" target="_blank"} +::note{to="https://github.com/nuxt/icon?tab=readme-ov-file#custom-local-collections" target="_blank"} Read more about this in the `@nuxt/icon` documentation. :: diff --git a/docs/content/1.getting-started/5.fonts.md b/docs/content/1.getting-started/5.fonts.md index c5984cb9..e6a45479 100644 --- a/docs/content/1.getting-started/5.fonts.md +++ b/docs/content/1.getting-started/5.fonts.md @@ -25,6 +25,6 @@ Nuxt UI automatically registers the `@nuxt/fonts` module for you, so there's no That's it! Nuxt Fonts will detect this and you should immediately see the web font loaded in your browser. -::tip{to="https://fonts.nuxt.com/advanced" target="_blank"} +::note{to="https://fonts.nuxt.com/advanced" target="_blank"} Read more about how `@nuxt/fonts` work behind the scenes to optimize your fonts. :: diff --git a/docs/content/3.components/alert.md b/docs/content/3.components/alert.md index c7ffbe5d..fcbc5e46 100644 --- a/docs/content/3.components/alert.md +++ b/docs/content/3.components/alert.md @@ -278,6 +278,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/docs/content/3.components/breadcrumb.md b/docs/content/3.components/breadcrumb.md index 46980c07..ea36b81b 100644 --- a/docs/content/3.components/breadcrumb.md +++ b/docs/content/3.components/breadcrumb.md @@ -123,6 +123,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/docs/content/3.components/button.md b/docs/content/3.components/button.md index 9694467a..ab886986 100644 --- a/docs/content/3.components/button.md +++ b/docs/content/3.components/button.md @@ -248,6 +248,7 @@ ignore: - exactHash - external - active + - onClick --- :: diff --git a/docs/content/3.components/carousel.md b/docs/content/3.components/carousel.md index fe1b7224..08420dff 100644 --- a/docs/content/3.components/carousel.md +++ b/docs/content/3.components/carousel.md @@ -1,34 +1,419 @@ --- -description: A carousel with motion and swipe support. +description: A carousel with motion and swipe built using Embla. links: + - label: Embla + to: https://www.embla-carousel.com/api/ + icon: i-custom-embla-carousel - label: GitHub icon: i-simple-icons-github to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Carousel.vue -navigation: - badge: - label: Todo - color: neutral - disabled: true --- ## Usage -## Examples +### Items - +:component-theme diff --git a/docs/content/3.components/command-palette.md b/docs/content/3.components/command-palette.md index 83fb82ac..4139e43f 100644 --- a/docs/content/3.components/command-palette.md +++ b/docs/content/3.components/command-palette.md @@ -561,6 +561,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/docs/content/3.components/context-menu.md b/docs/content/3.components/context-menu.md index 66952c90..42e48e65 100644 --- a/docs/content/3.components/context-menu.md +++ b/docs/content/3.components/context-menu.md @@ -292,6 +292,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/docs/content/3.components/dropdown-menu.md b/docs/content/3.components/dropdown-menu.md index 06a4f16b..9e70153e 100644 --- a/docs/content/3.components/dropdown-menu.md +++ b/docs/content/3.components/dropdown-menu.md @@ -361,6 +361,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/docs/content/3.components/modal.md b/docs/content/3.components/modal.md index ab498bcf..f5839715 100644 --- a/docs/content/3.components/modal.md +++ b/docs/content/3.components/modal.md @@ -381,6 +381,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/docs/content/3.components/slideover.md b/docs/content/3.components/slideover.md index c8df66f8..90d3581a 100644 --- a/docs/content/3.components/slideover.md +++ b/docs/content/3.components/slideover.md @@ -369,6 +369,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/docs/content/3.components/toast.md b/docs/content/3.components/toast.md index a9cb84ca..8f2f9c2b 100644 --- a/docs/content/3.components/toast.md +++ b/docs/content/3.components/toast.md @@ -239,6 +239,7 @@ ignore: - exactQuery - exactHash - external + - onClick --- :: diff --git a/package.json b/package.json index b6ad8fea..92dd4336 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@vueuse/core": "^11.1.0", "@vueuse/integrations": "^11.1.0", "defu": "^6.1.4", + "embla-carousel-vue": "^8.3.0", "fuse.js": "^7.0.0", "ohash": "^1.1.4", "radix-vue": "^1.9.7", @@ -70,6 +71,13 @@ "@release-it/conventional-changelog": "^8.0.2", "@standard-schema/spec": "1.0.0-beta.1", "@vue/test-utils": "^2.4.6", + "embla-carousel": "^8.3.0", + "embla-carousel-auto-height": "^8.3.0", + "embla-carousel-auto-scroll": "^8.3.0", + "embla-carousel-autoplay": "^8.3.0", + "embla-carousel-class-names": "^8.3.0", + "embla-carousel-fade": "^8.3.0", + "embla-carousel-wheel-gestures": "^8.0.1", "eslint": "^9.11.1", "happy-dom": "^15.7.4", "joi": "^17.13.3", diff --git a/playground/app/app.vue b/playground/app/app.vue index 7f9aae63..b8f65cbb 100644 --- a/playground/app/app.vue +++ b/playground/app/app.vue @@ -13,6 +13,7 @@ const components = [ 'button', 'button-group', 'card', + 'carousel', 'checkbox', 'chip', 'collapsible', diff --git a/playground/app/pages/components/carousel.vue b/playground/app/pages/components/carousel.vue new file mode 100644 index 00000000..a3d17576 --- /dev/null +++ b/playground/app/pages/components/carousel.vue @@ -0,0 +1,84 @@ + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4ceebd1..c234d89e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: defu: specifier: ^6.1.4 version: 6.1.4 + embla-carousel-vue: + specifier: ^8.3.0 + version: 8.3.0(vue@3.5.11(typescript@5.6.2)) fuse.js: specifier: ^7.0.0 version: 7.0.0 @@ -86,6 +89,27 @@ importers: '@vue/test-utils': specifier: ^2.4.6 version: 2.4.6 + embla-carousel: + specifier: ^8.3.0 + version: 8.3.0 + embla-carousel-auto-height: + specifier: ^8.3.0 + version: 8.3.0(embla-carousel@8.3.0) + embla-carousel-auto-scroll: + specifier: ^8.3.0 + version: 8.3.0(embla-carousel@8.3.0) + embla-carousel-autoplay: + specifier: ^8.3.0 + version: 8.3.0(embla-carousel@8.3.0) + embla-carousel-class-names: + specifier: ^8.3.0 + version: 8.3.0(embla-carousel@8.3.0) + embla-carousel-fade: + specifier: ^8.3.0 + version: 8.3.0(embla-carousel@8.3.0) + embla-carousel-wheel-gestures: + specifier: ^8.0.1 + version: 8.0.1(embla-carousel@8.3.0) eslint: specifier: ^9.11.1 version: 9.12.0(jiti@2.3.3) @@ -3276,6 +3300,50 @@ packages: electron-to-chromium@1.5.33: resolution: {integrity: sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==} + embla-carousel-auto-height@8.3.0: + resolution: {integrity: sha512-RbRu1AK5eC1ysBYysVKHMAl2PvtvbqE3GDa6mYyfuD5nDrLrRG/UHjElW8Elh/WYeJZ6AGW6Vb5SnWVwPsHnDg==} + peerDependencies: + embla-carousel: 8.3.0 + + embla-carousel-auto-scroll@8.3.0: + resolution: {integrity: sha512-ybXWqCTWvl+DeGwtGw0tUU1bOKglS/Ov8F5fueMkiwySKrSFAHizdqSrlMR1SQEXNZHMPH9LqCz7MajNYkdeAQ==} + peerDependencies: + embla-carousel: 8.3.0 + + embla-carousel-autoplay@8.3.0: + resolution: {integrity: sha512-h7DFJLf9uQD+XDxr1NwA3/oFIjsnj/iED2RjET5u6/svMec46IbF1CYPhmB5Q/1Fc0WkcvhPpsEsrtVXQLxNzA==} + peerDependencies: + embla-carousel: 8.3.0 + + embla-carousel-class-names@8.3.0: + resolution: {integrity: sha512-d78aB1rZyuvsgfyqzZJiNL8dvZsjWlF9IP62S9pxhhTwsh4Ry5AAYhPSWPjBfuFnOtnq72QDI06ziUuU6wdxHQ==} + peerDependencies: + embla-carousel: 8.3.0 + + embla-carousel-fade@8.3.0: + resolution: {integrity: sha512-m0NbkNPTAr6ghINhJrCnI0BRgWWoGRIGUd1tYCxTK00Exm9+kzOVL5KBPkrMVzXRXHe6TRgkmsCkb/7npfwRFQ==} + peerDependencies: + embla-carousel: 8.3.0 + + embla-carousel-reactive-utils@8.3.0: + resolution: {integrity: sha512-EYdhhJ302SC4Lmkx8GRsp0sjUhEN4WyFXPOk0kGu9OXZSRMmcBlRgTvHcq8eKJE1bXWBsOi1T83B+BSSVZSmwQ==} + peerDependencies: + embla-carousel: 8.3.0 + + embla-carousel-vue@8.3.0: + resolution: {integrity: sha512-K8ghzWZ9Th2Czvy9dVaz6ItgvFdkI3wHQe6HgeHmvmOlkvhu/fNz83uHrLWVAsjztu/b+yiFk9zvXb0+IQ2mPw==} + peerDependencies: + vue: ^3.2.37 + + embla-carousel-wheel-gestures@8.0.1: + resolution: {integrity: sha512-LMAnruDqDmsjL6UoQD65aLotpmfO49Fsr3H0bMi7I+BH6jbv9OJiE61kN56daKsVtCQEt0SU1MrJslbhtgF3yQ==} + engines: {node: '>=10'} + peerDependencies: + embla-carousel: ^8.0.0 || ~8.0.0-rc03 + + embla-carousel@8.3.0: + resolution: {integrity: sha512-Ve8dhI4w28qBqR8J+aMtv7rLK89r1ZA5HocwFz6uMB/i5EiC7bGI7y+AM80yAVUJw3qqaZYK7clmZMUR8kM3UA==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -6574,6 +6642,10 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + wheel-gestures@2.2.48: + resolution: {integrity: sha512-f+Gy33Oa5Z14XY9679Zze+7VFhbsQfBFXodnU2x589l4kxGM9L5Y8zETTmcMR5pWOPQyRv4Z0lNax6xCO0NSlA==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -10058,6 +10130,43 @@ snapshots: electron-to-chromium@1.5.33: {} + embla-carousel-auto-height@8.3.0(embla-carousel@8.3.0): + dependencies: + embla-carousel: 8.3.0 + + embla-carousel-auto-scroll@8.3.0(embla-carousel@8.3.0): + dependencies: + embla-carousel: 8.3.0 + + embla-carousel-autoplay@8.3.0(embla-carousel@8.3.0): + dependencies: + embla-carousel: 8.3.0 + + embla-carousel-class-names@8.3.0(embla-carousel@8.3.0): + dependencies: + embla-carousel: 8.3.0 + + embla-carousel-fade@8.3.0(embla-carousel@8.3.0): + dependencies: + embla-carousel: 8.3.0 + + embla-carousel-reactive-utils@8.3.0(embla-carousel@8.3.0): + dependencies: + embla-carousel: 8.3.0 + + embla-carousel-vue@8.3.0(vue@3.5.11(typescript@5.6.2)): + dependencies: + embla-carousel: 8.3.0 + embla-carousel-reactive-utils: 8.3.0(embla-carousel@8.3.0) + vue: 3.5.11(typescript@5.6.2) + + embla-carousel-wheel-gestures@8.0.1(embla-carousel@8.3.0): + dependencies: + embla-carousel: 8.3.0 + wheel-gestures: 2.2.48 + + embla-carousel@8.3.0: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -14283,6 +14392,8 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + wheel-gestures@2.2.48: {} + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/src/runtime/components/Breadcrumb.vue b/src/runtime/components/Breadcrumb.vue index 61215eb6..6c909eb5 100644 --- a/src/runtime/components/Breadcrumb.vue +++ b/src/runtime/components/Breadcrumb.vue @@ -5,7 +5,7 @@ import type { AppConfig } from '@nuxt/schema' import _appConfig from '#build/app.config' import theme from '#build/ui/breadcrumb' import type { AvatarProps, LinkProps } from '../types' -import type { DynamicSlots } from '../types/utils' +import type { DynamicSlots, PartialString } from '../types/utils' const appConfig = _appConfig as AppConfig & { ui: { breadcrumb: Partial } } @@ -31,7 +31,7 @@ export interface BreadcrumbProps { */ separatorIcon?: string class?: any - ui?: Partial + ui?: PartialString } type SlotProps = (props: { item: T, index: number, active?: boolean }) => any diff --git a/src/runtime/components/Carousel.vue b/src/runtime/components/Carousel.vue new file mode 100644 index 00000000..db88d7a2 --- /dev/null +++ b/src/runtime/components/Carousel.vue @@ -0,0 +1,306 @@ + + + + + + diff --git a/src/runtime/components/ContextMenu.vue b/src/runtime/components/ContextMenu.vue index e2a05a17..9a0c897d 100644 --- a/src/runtime/components/ContextMenu.vue +++ b/src/runtime/components/ContextMenu.vue @@ -6,7 +6,7 @@ import type { AppConfig } from '@nuxt/schema' import _appConfig from '#build/app.config' import theme from '#build/ui/context-menu' import type { AvatarProps, KbdProps, LinkProps } from '../types' -import type { DynamicSlots } from '../types/utils' +import type { DynamicSlots, PartialString } from '../types/utils' const appConfig = _appConfig as AppConfig & { ui: { contextMenu: Partial } } @@ -43,7 +43,7 @@ export interface ContextMenuProps extends Omit, */ portal?: boolean class?: any - ui?: Partial + ui?: PartialString } export interface ContextMenuEmits extends ContextMenuRootEmits {} diff --git a/src/runtime/components/DropdownMenu.vue b/src/runtime/components/DropdownMenu.vue index 99c697c9..2cf3d559 100644 --- a/src/runtime/components/DropdownMenu.vue +++ b/src/runtime/components/DropdownMenu.vue @@ -6,7 +6,7 @@ import type { AppConfig } from '@nuxt/schema' import _appConfig from '#build/app.config' import theme from '#build/ui/dropdown-menu' import type { AvatarProps, KbdProps, LinkProps } from '../types' -import type { DynamicSlots } from '../types/utils' +import type { DynamicSlots, PartialString } from '../types/utils' const appConfig = _appConfig as AppConfig & { ui: { dropdownMenu: Partial } } @@ -51,7 +51,7 @@ export interface DropdownMenuProps extends Omit */ portal?: boolean class?: any - ui?: Partial + ui?: PartialString } export interface DropdownMenuEmits extends DropdownMenuRootEmits {} diff --git a/src/runtime/components/NavigationMenu.vue b/src/runtime/components/NavigationMenu.vue index d20f5a22..f0d49c26 100644 --- a/src/runtime/components/NavigationMenu.vue +++ b/src/runtime/components/NavigationMenu.vue @@ -6,7 +6,7 @@ import type { AppConfig } from '@nuxt/schema' import _appConfig from '#build/app.config' import theme from '#build/ui/navigation-menu' import type { AvatarProps, BadgeProps, LinkProps } from '../types' -import type { DynamicSlots } from '../types/utils' +import type { DynamicSlots, PartialString } from '../types/utils' const appConfig = _appConfig as AppConfig & { ui: { navigationMenu: Partial } } @@ -62,7 +62,7 @@ export interface NavigationMenuProps extends Pick + ui?: PartialString } export interface NavigationMenuEmits extends NavigationMenuRootEmits {} diff --git a/src/runtime/types/index.ts b/src/runtime/types/index.ts index dc8dc09f..bf0a1ee6 100644 --- a/src/runtime/types/index.ts +++ b/src/runtime/types/index.ts @@ -7,6 +7,7 @@ export * from '../components/Badge.vue' export * from '../components/Breadcrumb.vue' export * from '../components/Button.vue' export * from '../components/Card.vue' +export * from '../components/Carousel.vue' export * from '../components/Checkbox.vue' export * from '../components/Chip.vue' export * from '../components/Collapsible.vue' diff --git a/src/theme/carousel.ts b/src/theme/carousel.ts new file mode 100644 index 00000000..e174b66b --- /dev/null +++ b/src/theme/carousel.ts @@ -0,0 +1,40 @@ +import type { ModuleOptions } from '../module' + +export default (options: Required) => ({ + slots: { + root: 'relative focus:outline-none', + viewport: 'overflow-hidden', + container: 'flex items-start', + item: 'min-w-0 shrink-0 basis-full', + controls: '', + arrows: '', + prev: 'absolute rounded-full', + next: 'absolute rounded-full', + dots: 'absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3', + dot: ['cursor-pointer size-3 bg-[--ui-border-accented] rounded-full', options.theme.transitions && 'transition'] + }, + variants: { + orientation: { + vertical: { + container: 'flex-col -mt-4', + item: 'pt-4', + prev: '-top-12 left-1/2 -translate-x-1/2 rotate-90', + next: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90' + }, + horizontal: { + container: 'flex-row -ml-4', + item: 'pl-4', + prev: '-left-12 top-1/2 -translate-y-1/2', + next: '-right-12 top-1/2 -translate-y-1/2' + } + }, + active: { + true: { + dot: 'bg-[--ui-border-inverted]' + } + } + }, + defaultVariants: { + orientation: 'horizontal' + } +}) diff --git a/src/theme/icons.ts b/src/theme/icons.ts index 4c002ff8..203b0975 100644 --- a/src/theme/icons.ts +++ b/src/theme/icons.ts @@ -4,6 +4,8 @@ export default { chevronDown: 'i-heroicons-chevron-down-20-solid', chevronLeft: 'i-heroicons-chevron-left-20-solid', chevronRight: 'i-heroicons-chevron-right-20-solid', + arrowLeft: 'i-heroicons-arrow-left-20-solid', + arrowRight: 'i-heroicons-arrow-right-20-solid', check: 'i-heroicons-check-20-solid', close: 'i-heroicons-x-mark-20-solid', ellipsis: 'i-heroicons-ellipsis-horizontal-20-solid', diff --git a/src/theme/index.ts b/src/theme/index.ts index 2354152c..8ed54a2e 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -7,6 +7,7 @@ export { default as breadcrumb } from './breadcrumb' export { default as button } from './button' export { default as buttonGroup } from './button-group' export { default as card } from './card' +export { default as carousel } from './carousel' export { default as checkbox } from './checkbox' export { default as chip } from './chip' export { default as collapsible } from './collapsible' diff --git a/test/components/Carousel.spec.ts b/test/components/Carousel.spec.ts new file mode 100644 index 00000000..b0bf999c --- /dev/null +++ b/test/components/Carousel.spec.ts @@ -0,0 +1,44 @@ +import { defineComponent } from 'vue' +import { describe, it, expect } from 'vitest' +import Carousel, { type CarouselProps, type CarouselSlots } from '../../src/runtime/components/Carousel.vue' +import ComponentRender from '../component-render' + +const CarouselWrapper = defineComponent({ + components: { + UCarousel: Carousel as any + }, + template: ` + +` +}) + +describe('Carousel', () => { + const items = [ + { src: 'https://picsum.photos/600/600?random=1' }, + { src: 'https://picsum.photos/600/600?random=2' }, + { src: 'https://picsum.photos/600/600?random=3' }, + { src: 'https://picsum.photos/600/600?random=4' }, + { src: 'https://picsum.photos/600/600?random=5' }, + { src: 'https://picsum.photos/600/600?random=6' } + ] + + const props = { items } + + it.each([ + // Props + ['with items', { props }], + ['with orientation vertical', { props: { ...props, orientation: 'vertical' as const } }], + ['with arrows', { props: { ...props, arrows: true } }], + ['with prev', { props: { ...props, arrows: true, prev: { color: 'primary' as const } } }], + ['with prevIcon', { props: { ...props, arrows: true, prevIcon: 'i-heroicons-arrow-left' } }], + ['with next', { props: { ...props, arrows: true, next: { color: 'primary' as const } } }], + ['with nextIcon', { props: { ...props, arrows: true, nextIcon: 'i-heroicons-arrow-right' } }], + ['with dots', { props: { ...props, dots: true } }], + ['with as', { props: { ...props, as: 'nav' } }], + ['with class', { props: { ...props, class: 'w-full max-w-xs' } }], + ['with ui', { props: { ...props, ui: { viewport: 'h-[320px]' } } }] + ])('renders %s correctly', async (nameOrHtml: string, options: { props?: CarouselProps, slots?: Partial> }) => { + const html = await ComponentRender(nameOrHtml, options, CarouselWrapper) + expect(html).toMatchSnapshot() + }) +}) diff --git a/test/components/__snapshots__/Carousel.spec.ts.snap b/test/components/__snapshots__/Carousel.spec.ts.snap new file mode 100644 index 00000000..4ad42e06 --- /dev/null +++ b/test/components/__snapshots__/Carousel.spec.ts.snap @@ -0,0 +1,225 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Carousel > renders with arrows correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
" +`; + +exports[`Carousel > renders with as correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+ +
" +`; + +exports[`Carousel > renders with class correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+ +
" +`; + +exports[`Carousel > renders with dots correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
" +`; + +exports[`Carousel > renders with items correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+ +
" +`; + +exports[`Carousel > renders with next correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
" +`; + +exports[`Carousel > renders with nextIcon correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
" +`; + +exports[`Carousel > renders with orientation vertical correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+ +
" +`; + +exports[`Carousel > renders with prev correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
" +`; + +exports[`Carousel > renders with prevIcon correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
" +`; + +exports[`Carousel > renders with ui correctly 1`] = ` +"
+
+
+
+
+
+
+
+
+
+
+ +
" +`;