feat(Carousel): implement component (#2288)

This commit is contained in:
Benjamin Canac
2024-10-08 17:12:43 +02:00
committed by GitHub
parent 69a6e11c52
commit 68ee3f11ca
43 changed files with 1553 additions and 27 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel v-slot="{ item }" arrows :items="items" class="w-full max-w-xs mx-auto">
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/320?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/320?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/320?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
auto-height
arrows
dots
:items="items"
:ui="{
container: 'transition-[height]',
controls: 'absolute -top-8 inset-x-12',
dots: '-top-7',
dot: 'w-6 h-1'
}"
class="w-full max-w-xs mx-auto"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/468/468?random=1',
'https://picsum.photos/468/468?random=2',
'https://picsum.photos/468/468?random=3',
'https://picsum.photos/468/468?random=4',
'https://picsum.photos/468/468?random=5',
'https://picsum.photos/468/468?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
loop
dots
arrows
auto-scroll
:items="items"
:ui="{ item: 'basis-1/3' }"
>
<img :src="item" width="234" height="234" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/468/468?random=1',
'https://picsum.photos/468/468?random=2',
'https://picsum.photos/468/468?random=3',
'https://picsum.photos/468/468?random=4',
'https://picsum.photos/468/468?random=5',
'https://picsum.photos/468/468?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
loop
arrows
dots
:autoplay="{ delay: 2000 }"
:items="items"
:ui="{ item: 'basis-1/3' }"
>
<img :src="item" width="234" height="234" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/528/528?random=1',
'https://picsum.photos/528/528?random=2',
'https://picsum.photos/528/528?random=3',
'https://picsum.photos/528/528?random=4',
'https://picsum.photos/528/528?random=5',
'https://picsum.photos/528/528?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
class-names
arrows
:items="items"
:ui="{
item: 'basis-[70%] transition-opacity [&:not(.is-snapped)]:opacity-10'
}"
class="mx-auto max-w-sm"
>
<img :src="item" width="264" height="264" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel v-slot="{ item }" dots :items="items" class="w-full max-w-xs mx-auto">
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel v-slot="{ item }" dots :items="items" :ui="{ item: 'basis-1/3' }">
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
fade
arrows
dots
:items="items"
class="w-full max-w-xs mx-auto"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel v-slot="{ item }" :items="items" class="w-full max-w-xs mx-auto">
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/468/468?random=1',
'https://picsum.photos/468/468?random=2',
'https://picsum.photos/468/468?random=3',
'https://picsum.photos/468/468?random=4',
'https://picsum.photos/468/468?random=5',
'https://picsum.photos/468/468?random=6'
]
</script>
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-1/3' }">
<img :src="item" width="234" height="234" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
orientation="vertical"
:items="items"
class="w-full max-w-xs mx-auto"
:ui="{ container: 'h-[336px]' }"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
arrows
:prev="{ color: 'primary' }"
:next="{ variant: 'solid' }"
:items="items"
class="w-full max-w-xs mx-auto"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
defineProps<{
prevIcon?: string
nextIcon?: string
}>()
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
arrows
:prev-icon="prevIcon"
:next-icon="nextIcon"
:items="items"
class="w-full max-w-xs mx-auto"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/468/468?random=1',
'https://picsum.photos/468/468?random=2',
'https://picsum.photos/468/468?random=3',
'https://picsum.photos/468/468?random=4',
'https://picsum.photos/468/468?random=5',
'https://picsum.photos/468/468?random=6'
]
</script>
<template>
<UCarousel
v-slot="{ item }"
loop
wheel-gestures
:items="items"
:ui="{ item: 'basis-1/3' }"
>
<img :src="item" width="234" height="234" class="rounded-lg">
</UCarousel>
</template>

View File

@@ -36,7 +36,7 @@ Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your
</style>
```
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.
::

View File

@@ -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:
</template>
```
::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.
::

View File

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

View File

@@ -278,6 +278,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

@@ -123,6 +123,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

@@ -248,6 +248,7 @@ ignore:
- exactHash
- external
- active
- onClick
---
::

View File

@@ -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
<!-- ## API
Use the `items` prop as an array and render each item using the default slot:
::note
Use your mouse to drag the carousel horizontally on desktop.
::
::component-example
---
name: 'carousel-items-example'
class: 'p-8'
---
::
You can control how many items are visible by using the [`basis`](https://tailwindcss.com/docs/flex-basis) / [`width`](https://tailwindcss.com/docs/width) utility classes on the `item`:
::component-example
---
name: 'carousel-items-multiple-example'
class: 'p-8 px-16'
---
::
### Orientation
Use the `orientation` prop to change the orientation of the Progress. Defaults to `horizontal`.
::note
Use your mouse to drag the carousel vertically on desktop.
::
::component-example
---
name: 'carousel-orientation-example'
class: 'p-8'
---
::
::caution
You need to specify a `height` on the container in vertical orientation.
::
### Arrows
Use the `arrows` prop to display prev and next buttons.
::component-example
---
name: 'carousel-arrows-example'
class: 'p-8'
---
::
### Prev / Next
Use the `prev` and `next` props to customize the prev and next buttons.
::component-example
---
name: 'carousel-prev-next-example'
class: 'p-8'
---
::
### Prev Icon / Next Icon
Use the `prev-icon` and `next-icon` props to customize the buttons [Icon](/components/icon). Defaults to `i-heroicons-arrow-left-20-solid` / `i-heroicons-arrow-right-20-solid`.
::component-example
---
name: 'carousel-prev-next-icon-example'
class: 'p-8'
options:
- name: 'prevIcon'
label: 'prevIcon'
default: 'i-heroicons-chevron-left'
- name: 'nextIcon'
label: 'nextIcon'
default: 'i-heroicons-chevron-right'
---
::
::tip{to="/getting-started/icons#theme"}
You can customize these icons globally in your `app.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key.
::
### Dots
Use the `dots` prop to display a list of dots to scroll to a specific slide.
::component-example
---
name: 'carousel-dots-example'
class: 'p-8 pb-12'
---
::
The number of dots is based on the number of slides displayed in the view:
::component-example
---
name: 'carousel-dots-multiple-example'
class: 'p-8 px-16 pb-12'
---
::
## Plugins
The Carousel component implements the official [Embla Carousel plugins](https://www.embla-carousel.com/plugins/).
### Autoplay
This plugin is used to extend Embla Carousel with **autoplay** functionality.
::warning
Install the `embla-carousel-autoplay` package:
::code-group
```bash [pnpm]
pnpm add embla-carousel-autoplay
```
```bash [yarn]
yarn add embla-carousel-autoplay
```
```bash [npm]
npm install embla-carousel-autoplay
```
```bash [bun]
bun add embla-carousel-autoplay
```
::
::
Use the `autoplay` prop as a boolean or an object to configure the [Autoplay plugin](https://www.embla-carousel.com/plugins/autoplay/).
::component-example
---
name: 'carousel-autoplay-example'
class: 'p-8 px-16 pb-12'
---
::
::note
In this example, we're using the `loop` prop for an infinite carousel.
::
### Auto Scroll
This plugin is used to extend Embla Carousel with **auto scroll** functionality.
::warning
Install the `embla-carousel-auto-scroll` package:
::code-group
```bash [pnpm]
pnpm add embla-carousel-auto-scroll
```
```bash [yarn]
yarn add embla-carousel-auto-scroll
```
```bash [npm]
npm install embla-carousel-auto-scroll
```
```bash [bun]
bun add embla-carousel-auto-scroll
```
::
::
Use the `auto-scroll` prop as a boolean or an object to configure the [Auto Scroll plugin](https://www.embla-carousel.com/plugins/auto-scroll/).
::component-example
---
name: 'carousel-auto-scroll-example'
class: 'p-8 px-16 pb-12'
---
::
::note
In this example, we're using the `loop` prop for an infinite carousel.
::
### Auto Height
This plugin is used to extend Embla Carousel with **auto height** functionality. It changes the height of the carousel container to fit the height of the highest slide in view.
::warning
Install the `embla-carousel-auto-height` package:
::code-group
```bash [pnpm]
pnpm add embla-carousel-auto-height
```
```bash [yarn]
yarn add embla-carousel-auto-height
```
```bash [npm]
npm install embla-carousel-auto-height
```
```bash [bun]
bun add embla-carousel-auto-height
```
::
::
Use the `auto-height` prop as a boolean or an object to configure the [Auto Height plugin](https://www.embla-carousel.com/plugins/auto-height/).
::component-example
---
name: 'carousel-auto-height-example'
class: 'p-8 pt-16'
---
::
::note
In this example, we add the `transition-[height]` class on the container to animate the height change.
::
### Class Names
Class Names is a **class name toggle** utility plugin for Embla Carousel that enables you to automate the toggling of class names on your carousel.
::warning
Install the `embla-carousel-class-names` package:
::code-group
```bash [pnpm]
pnpm add embla-carousel-class-names
```
```bash [yarn]
yarn add embla-carousel-class-names
```
```bash [npm]
npm install embla-carousel-class-names
```
```bash [bun]
bun add embla-carousel-class-names
```
::
::
Use the `class-names` prop as a boolean or an object to configure the [Class Names plugin](https://www.embla-carousel.com/plugins/class-names/).
::component-example
---
name: 'carousel-class-names-example'
class: 'p-8'
---
::
::note
In this example, we add the `transition-opacity [&:not(.is-snapped)]:opacity-10` classes on the `item` to animate the opacity change.
::
### Fade
This plugin is used to replace the Embla Carousel scroll functionality with **fade transitions**.
::warning
Install the `embla-carousel-fade` package:
::code-group
```bash [pnpm]
pnpm add embla-carousel-fade
```
```bash [yarn]
yarn add embla-carousel-fade
```
```bash [npm]
npm install embla-carousel-fade
```
```bash [bun]
bun add embla-carousel-fade
```
::
::
Use the `fade` prop as a boolean or an object to configure the [Fade plugin](https://www.embla-carousel.com/plugins/fade/).
::component-example
---
name: 'carousel-fade-example'
class: 'p-8 pb-12'
---
::
### Wheel Gestures
This plugin is used to extend Embla Carousel with the ability to **use the mouse/trackpad wheel** to navigate the carousel.
::warning
Install the `embla-carousel-wheel-gestures` package:
::code-group
```bash [pnpm]
pnpm add embla-carousel-wheel-gestures
```
```bash [yarn]
yarn add embla-carousel-wheel-gestures
```
```bash [npm]
npm install embla-carousel-wheel-gestures
```
```bash [bun]
bun add embla-carousel-wheel-gestures
```
::
::
Use the `wheel-gestures` prop as a boolean or an object to configure the [Wheel Gestures plugin](https://www.embla-carousel.com/plugins/wheel-gestures/).
::note
Use your mouse wheel to scroll the carousel.
::
::component-example
---
name: 'carousel-wheel-gestures-example'
class: 'p-8 px-16'
---
::
## API
### Props
:component-props
::component-props
---
ignore:
- as
- to
- target
- active
- activeClass
- inactiveClass
- exactActiveClass
- ariaCurrentValue
- href
- rel
- noRel
- prefetch
- prefetchOn
- noPrefetch
- prefetchedClass
- replace
- exact
- exactQuery
- exactHash
- external
- onClick
---
::
### Slots
:component-slots
### Emits
### Expose
:component-emits
When accessing the component via a template ref, you can use the following:
| Name | Type |
| ---- | ---- |
| `emblaRef`{lang="ts-type"} | `Ref<HTMLElement \| null>`{lang="ts-type"} |
| `emblaApi`{lang="ts-type"} | [`Ref<EmblaCarouselType \| null>`{lang="ts-type"}](https://www.embla-carousel.com/api/methods/#typescript) |
## Theme
:component-theme -->
:component-theme

View File

@@ -561,6 +561,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

@@ -292,6 +292,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

@@ -361,6 +361,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

@@ -381,6 +381,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

@@ -369,6 +369,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

@@ -239,6 +239,7 @@ ignore:
- exactQuery
- exactHash
- external
- onClick
---
::

View File

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

View File

@@ -13,6 +13,7 @@ const components = [
'button',
'button-group',
'card',
'carousel',
'checkbox',
'chip',
'collapsible',

View File

@@ -0,0 +1,84 @@
<script setup lang="ts">
import theme from '#build/ui/navigation-menu'
const orientations = Object.keys(theme.variants.orientation)
const orientation = ref('horizontal' as const)
const loop = ref(false)
const skipSnaps = ref(false)
const autoplay = ref(false)
const autoScroll = ref(false)
const autoHeight = ref(false)
const fade = ref(false)
const wheelGestures = ref(false)
const classNames = ref(false)
const arrows = ref(false)
const dots = ref(false)
const bind = computed(() => ({
loop: loop.value,
skipSnaps: skipSnaps.value,
orientation: orientation.value,
autoplay: autoplay.value,
autoScroll: autoScroll.value,
autoHeight: autoHeight.value,
fade: fade.value,
wheelGestures: wheelGestures.value,
classNames: classNames.value,
arrows: arrows.value,
dots: dots.value
}))
const items = Array.from({ length: 6 }).map((_, index) => index)
</script>
<template>
<div class="space-y-11">
<div class="space-y-3">
<div class="flex justify-center">
<USelect v-model="orientation" :items="orientations" placeholder="Orientation" />
</div>
<fieldset class="flex items-center justify-center gap-2">
<legend class="text-xs font-mono font-bold text-center mb-2">
Options
</legend>
<USwitch v-model="loop" label="Loop" />
<USwitch v-model="skipSnaps" label="Skip Snaps" />
<USwitch v-model="arrows" label="Arrows" />
<USwitch v-model="dots" label="Dots" />
</fieldset>
<fieldset class="flex items-center justify-center gap-2">
<legend class="text-xs font-mono font-bold text-center mb-2">
Plugins
</legend>
<USwitch v-model="autoplay" label="Autoplay" />
<USwitch v-model="autoScroll" label="Auto Scroll" />
<USwitch v-model="autoHeight" label="Auto Height" :disabled="orientation !== 'horizontal'" />
<USwitch v-model="fade" label="Fade" />
<USwitch v-model="classNames" label="Class Names" />
<USwitch v-model="wheelGestures" label="Wheel Gestures" />
</fieldset>
</div>
<template v-if="classNames">
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" :ui="{ item: 'basis-[70%] transition-opacity ease-in-out [&:not(.is-snapped)]:opacity-10', container: 'h-[352px]' }" class="w-full max-w-xl mx-auto">
<img :src="`https://picsum.photos/600/350?v=${index}`" class="rounded-lg">
</UCarousel>
</template>
<template v-else-if="autoHeight">
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" :ui="{ container: 'transition-[height] duration-200' }" class="w-full max-w-md mx-auto">
<img :src="`https://picsum.photos/600/${index % 2 === 0 ? 350 : 450}?v=${index}`" :class="index % 2 === 0 ? 'h-[350px]' : 'h-[450px]'" class="rounded-lg">
</UCarousel>
</template>
<template v-else>
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" class="w-[320px] mx-auto" :ui="{ container: 'h-[336px]' }">
<img :src="`https://picsum.photos/640/640?v=${index}`" class="rounded-lg">
</UCarousel>
<template v-if="orientation === 'horizontal'">
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" :ui="{ item: 'basis-1/3' }" class="w-full max-w-xs mx-auto">
<img :src="`https://picsum.photos/320/320?v=${index}`" class="rounded-lg">
</UCarousel>
</template>
</template>
</div>
</template>

111
pnpm-lock.yaml generated
View File

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

View File

@@ -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<typeof theme> } }
@@ -31,7 +31,7 @@ export interface BreadcrumbProps<T> {
*/
separatorIcon?: string
class?: any
ui?: Partial<typeof breadcrumb.slots>
ui?: PartialString<typeof breadcrumb.slots>
}
type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any

View File

@@ -0,0 +1,306 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import type { EmblaCarouselType, EmblaOptionsType, EmblaPluginType } from 'embla-carousel'
import type { AutoplayOptionsType } from 'embla-carousel-autoplay'
import type { AutoScrollOptionsType } from 'embla-carousel-auto-scroll'
import type { AutoHeightOptionsType } from 'embla-carousel-auto-height'
import type { ClassNamesOptionsType } from 'embla-carousel-class-names'
import type { FadeOptionsType } from 'embla-carousel-fade'
import type { WheelGesturesPluginOptions } from 'embla-carousel-wheel-gestures'
import _appConfig from '#build/app.config'
import theme from '#build/ui/carousel'
import type { ButtonProps } from '../types'
import type { AcceptableValue, PartialString } from '../types/utils'
const appConfig = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
const carousel = tv({ extend: tv(theme), ...(appConfig.ui?.carousel || {}) })
type CarouselVariants = VariantProps<typeof carousel>
export interface CarouselProps<T> extends Omit<EmblaOptionsType, 'axis' | 'container' | 'slides' | 'direction'> {
/**
* Configure the prev button when arrows are enabled.
* @defaultValue { size: 'md', color: 'neutral', variant: 'link' }
*/
prev?: ButtonProps
/**
* The icon displayed in the prev button.
* @defaultValue appConfig.ui.icons.arrowLeft
*/
prevIcon?: string
/**
* Configure the next button when arrows are enabled.
* @defaultValue { size: 'md', color: 'neutral', variant: 'link' }
*/
next?: ButtonProps
/**
* The icon displayed in the next button.
* @defaultValue appConfig.ui.icons.arrowRight
*/
nextIcon?: string
/**
* Display prev and next buttons to scroll the carousel.
* @defaultValue false
*/
arrows?: boolean
/**
* Display dots to scroll to a specific slide.
* @defaultValue false
*/
dots?: boolean
orientation?: CarouselVariants['orientation']
items?: T[]
/**
* Enable Autoplay plugin
* @see https://www.embla-carousel.com/plugins/autoplay/
*/
autoplay?: boolean | AutoplayOptionsType
/**
* Enable Auto Scroll plugin
* @see https://www.embla-carousel.com/plugins/auto-scroll/
*/
autoScroll?: boolean | AutoScrollOptionsType
/**
* Enable Auto Height plugin
* @see https://www.embla-carousel.com/plugins/auto-height/
*/
autoHeight?: boolean | AutoHeightOptionsType
/**
* Enable Class Names plugin
* @see https://www.embla-carousel.com/plugins/class-names/
*/
classNames?: boolean | ClassNamesOptionsType
/**
* Enable Fade plugin
* @see https://www.embla-carousel.com/plugins/fade/
*/
fade?: boolean | FadeOptionsType
/**
* Enable Wheel Gestures plugin
* @see https://www.embla-carousel.com/plugins/wheel-gestures/
*/
wheelGestures?: boolean | WheelGesturesPluginOptions
class?: any
ui?: PartialString<typeof carousel.slots>
}
export type CarouselSlots<T> = {
default(props: { item: T, index: number }): any
}
</script>
<script setup lang="ts" generic="T extends AcceptableValue">
import { computed, ref, watch, onMounted } from 'vue'
import useEmblaCarousel from 'embla-carousel-vue'
import { useForwardProps } from 'radix-vue'
import { reactivePick, computedAsync } from '@vueuse/core'
import { useAppConfig } from '#imports'
import UButton from './Button.vue'
const props = withDefaults(defineProps<CarouselProps<T>>(), {
orientation: 'horizontal',
arrows: false,
dots: false,
// Embla Options
active: true,
align: 'center',
breakpoints: () => ({}),
containScroll: 'trimSnaps',
dragFree: false,
dragThreshold: 10,
duration: 25,
inViewThreshold: 0,
loop: false,
skipSnaps: false,
slidesToScroll: 1,
startIndex: 0,
watchDrag: true,
watchResize: true,
watchSlides: true,
watchFocus: true,
// Embla Plugins
autoplay: false,
autoScroll: false,
autoHeight: false,
classNames: false,
fade: false,
wheelGestures: false
})
defineSlots<CarouselSlots<T>>()
const appConfig = useAppConfig()
const rootProps = useForwardProps(reactivePick(props, 'active', 'align', 'breakpoints', 'containScroll', 'dragFree', 'dragThreshold', 'duration', 'inViewThreshold', 'loop', 'skipSnaps', 'slidesToScroll', 'startIndex', 'watchDrag', 'watchResize', 'watchSlides', 'watchFocus'))
const ui = computed(() => carousel({
orientation: props.orientation
}))
const options = computed<EmblaOptionsType>(() => ({
...(props.fade ? { align: 'center', containScroll: false } : {}),
...rootProps.value,
axis: props.orientation === 'horizontal' ? 'x' : 'y',
// TODO: Get from ConfigProvider
direction: 'ltr'
}))
const plugins = computedAsync<EmblaPluginType[]>(async () => {
const plugins = []
if (props.autoplay) {
const AutoplayPlugin = await import('embla-carousel-autoplay').then(r => r.default)
plugins.push(AutoplayPlugin(typeof props.autoplay === 'boolean' ? {} : props.autoplay))
}
if (props.autoScroll) {
const AutoScrollPlugin = await import('embla-carousel-auto-scroll').then(r => r.default)
plugins.push(AutoScrollPlugin(typeof props.autoScroll === 'boolean' ? {} : props.autoScroll))
}
if (props.autoHeight) {
const AutoHeightPlugin = await import('embla-carousel-auto-height').then(r => r.default)
plugins.push(AutoHeightPlugin(typeof props.autoHeight === 'boolean' ? {} : props.autoHeight))
}
if (props.classNames) {
const ClassNamesPlugin = await import('embla-carousel-class-names').then(r => r.default)
plugins.push(ClassNamesPlugin(typeof props.classNames === 'boolean' ? {} : props.classNames))
}
if (props.fade) {
const FadePlugin = await import('embla-carousel-fade').then(r => r.default)
plugins.push(FadePlugin(typeof props.fade === 'boolean' ? {} : props.fade))
}
if (props.wheelGestures) {
const { WheelGesturesPlugin } = await import('embla-carousel-wheel-gestures')
plugins.push(WheelGesturesPlugin(typeof props.wheelGestures === 'boolean' ? {} : props.wheelGestures))
}
return plugins
})
const [emblaRef, emblaApi] = useEmblaCarousel(options.value, plugins.value)
watch([options, plugins], () => {
emblaApi.value?.reInit(options.value, plugins.value)
})
function scrollPrev() {
emblaApi.value?.scrollPrev()
}
function scrollNext() {
emblaApi.value?.scrollNext()
}
function scrollTo(index: number) {
emblaApi.value?.scrollTo(index)
}
function onKeyDown(event: KeyboardEvent) {
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'
if (event.key === prevKey) {
event.preventDefault()
scrollPrev()
return
}
if (event.key === nextKey) {
event.preventDefault()
scrollNext()
}
}
const canScrollNext = ref(false)
const canScrollPrev = ref(false)
const selectedIndex = ref<number>(0)
const scrollSnaps = ref<number[]>([])
function onInit(api: EmblaCarouselType) {
scrollSnaps.value = api?.scrollSnapList() || []
}
function onSelect(api: EmblaCarouselType) {
canScrollNext.value = api?.canScrollNext() || false
canScrollPrev.value = api?.canScrollPrev() || false
selectedIndex.value = api?.selectedScrollSnap() || 0
}
onMounted(() => {
if (!emblaApi.value) {
return
}
emblaApi.value?.on('init', onInit)
emblaApi.value?.on('init', onSelect)
emblaApi.value?.on('reInit', onInit)
emblaApi.value?.on('reInit', onSelect)
emblaApi.value?.on('select', onSelect)
})
defineExpose({
emblaRef,
emblaApi
})
</script>
<template>
<div
role="region"
aria-roledescription="carousel"
tabindex="0"
:class="ui.root({ class: [props.class, props.ui?.root] })"
@keydown="onKeyDown"
>
<div ref="emblaRef" :class="ui.viewport({ class: props.ui?.viewport })">
<div :class="ui.container({ class: props.ui?.container })">
<div
v-for="(item, index) in items"
:key="index"
role="group"
aria-roledescription="slide"
:class="ui.item({ class: props.ui?.item })"
>
<slot :item="item" :index="index" />
</div>
</div>
</div>
<div v-if="arrows || dots" :class="ui.controls({ class: props.ui?.controls })">
<div v-if="arrows" :class="ui.arrows({ class: props.ui?.arrows })">
<UButton
:disabled="!canScrollPrev"
:icon="prevIcon || appConfig.ui.icons.arrowLeft"
size="md"
color="neutral"
variant="outline"
aria-label="Prev"
v-bind="typeof prev === 'object' ? prev : undefined"
:class="ui.prev({ class: props.ui?.prev })"
@click="scrollPrev"
/>
<UButton
:disabled="!canScrollNext"
:icon="nextIcon || appConfig.ui.icons.arrowRight"
size="md"
color="neutral"
variant="outline"
aria-label="Next"
v-bind="typeof next === 'object' ? next : undefined"
:class="ui.next({ class: props.ui?.next })"
@click="scrollNext"
/>
</div>
<div v-if="dots" :class="ui.dots({ class: props.ui?.dots })">
<template v-for="(_, index) in scrollSnaps" :key="index">
<button :class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })" @click="scrollTo(index)" />
</template>
</div>
</div>
</div>
</template>

View File

@@ -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<typeof theme> } }
@@ -43,7 +43,7 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'>,
*/
portal?: boolean
class?: any
ui?: Partial<typeof contextMenu.slots>
ui?: PartialString<typeof contextMenu.slots>
}
export interface ContextMenuEmits extends ContextMenuRootEmits {}

View File

@@ -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<typeof theme> } }
@@ -51,7 +51,7 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
*/
portal?: boolean
class?: any
ui?: Partial<typeof dropdownMenu.slots>
ui?: PartialString<typeof dropdownMenu.slots>
}
export interface DropdownMenuEmits extends DropdownMenuRootEmits {}

View File

@@ -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<typeof theme> } }
@@ -62,7 +62,7 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'd
*/
arrow?: boolean
class?: any
ui?: Partial<typeof navigationMenu.slots>
ui?: PartialString<typeof navigationMenu.slots>
}
export interface NavigationMenuEmits extends NavigationMenuRootEmits {}

View File

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

40
src/theme/carousel.ts Normal file
View File

@@ -0,0 +1,40 @@
import type { ModuleOptions } from '../module'
export default (options: Required<ModuleOptions>) => ({
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'
}
})

View File

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

View File

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

View File

@@ -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: `<UCarousel v-slot="{ item }">
<img :src="item.src" width="300" height="300" class="rounded-lg">
</UCarousel>`
})
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<typeof items[number]>, slots?: Partial<CarouselSlots<typeof items[number]>> }) => {
const html = await ComponentRender(nameOrHtml, options, CarouselWrapper)
expect(html).toMatchSnapshot()
})
})

View File

@@ -0,0 +1,225 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Carousel > renders with arrows correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<div class=""><button type="button" disabled="" aria-label="Prev" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -left-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-left-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button><button type="button" disabled="" aria-label="Next" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -right-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-right-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button></div>
<!--v-if-->
</div>
</div>"
`;
exports[`Carousel > renders with as correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none" as="nav">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<!--v-if-->
</div>"
`;
exports[`Carousel > renders with class correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none w-full max-w-xs">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<!--v-if-->
</div>"
`;
exports[`Carousel > renders with dots correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<!--v-if-->
<div class="absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3"></div>
</div>
</div>"
`;
exports[`Carousel > renders with items correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<!--v-if-->
</div>"
`;
exports[`Carousel > renders with next correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<div class=""><button type="button" disabled="" aria-label="Prev" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -left-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-left-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button><button type="button" disabled="" aria-label="Next" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-primary]/50 text-[--ui-primary] hover:bg-[--ui-primary]/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus-visible:ring-2 focus-visible:ring-[--ui-primary] p-1.5 absolute rounded-full -right-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-right-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button></div>
<!--v-if-->
</div>
</div>"
`;
exports[`Carousel > renders with nextIcon correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<div class=""><button type="button" disabled="" aria-label="Prev" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -left-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-left-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button><button type="button" disabled="" aria-label="Next" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -right-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-right shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button></div>
<!--v-if-->
</div>
</div>"
`;
exports[`Carousel > renders with orientation vertical correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-col -mt-4" style="transform: translate3d(0px,NaNpx,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pt-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pt-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pt-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pt-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pt-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pt-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<!--v-if-->
</div>"
`;
exports[`Carousel > renders with prev correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<div class=""><button type="button" disabled="" aria-label="Prev" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-primary]/50 text-[--ui-primary] hover:bg-[--ui-primary]/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus-visible:ring-2 focus-visible:ring-[--ui-primary] p-1.5 absolute rounded-full -left-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-left-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button><button type="button" disabled="" aria-label="Next" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -right-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-right-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button></div>
<!--v-if-->
</div>
</div>"
`;
exports[`Carousel > renders with prevIcon correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<div class=""><button type="button" disabled="" aria-label="Prev" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -left-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-left shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button><button type="button" disabled="" aria-label="Next" class="font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 ring ring-inset ring-[--ui-border-accented] text-[--ui-text] bg-[--ui-bg] hover:bg-[--ui-bg-elevated] disabled:bg-[--ui-bg] aria-disabled:bg-[--ui-bg] focus-visible:ring-2 focus-visible:ring-[--ui-border-inverted] p-1.5 absolute rounded-full -right-12 top-1/2 -translate-y-1/2"><span class="iconify i-heroicons:arrow-right-20-solid shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button></div>
<!--v-if-->
</div>
</div>"
`;
exports[`Carousel > renders with ui correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden h-[320px]">
<div class="flex items-start flex-row -ml-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full pl-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<!--v-if-->
</div>"
`;