diff --git a/apps/www/.vitepress/theme/config/docs.ts b/apps/www/.vitepress/theme/config/docs.ts index 52cddca3..b7f61e4e 100644 --- a/apps/www/.vitepress/theme/config/docs.ts +++ b/apps/www/.vitepress/theme/config/docs.ts @@ -168,6 +168,12 @@ export const docsConfig: DocsConfig = { href: '/docs/components/card', items: [], }, + { + title: 'Carousel', + href: '/docs/components/carousel', + label: 'New', + items: [], + }, { title: 'Checkbox', href: '/docs/components/checkbox', diff --git a/apps/www/.vitepress/theme/layout/MainLayout.vue b/apps/www/.vitepress/theme/layout/MainLayout.vue index 9a0b7a0c..23bc4121 100644 --- a/apps/www/.vitepress/theme/layout/MainLayout.vue +++ b/apps/www/.vitepress/theme/layout/MainLayout.vue @@ -49,7 +49,13 @@ const links = [ ] const isOpen = ref(false) -const { Meta_K, Ctrl_K } = useMagicKeys() +const { Meta_K, Ctrl_K } = useMagicKeys({ + passive: false, + onEventFired(e) { + if (e.key === 'k' && (e.metaKey || e.ctrlKey)) + e.preventDefault() + }, +}) watch([Meta_K, Ctrl_K], (v) => { if (v[0] || v[1]) diff --git a/apps/www/__registry__/index.ts b/apps/www/__registry__/index.ts index b24d3ade..9088c48d 100644 --- a/apps/www/__registry__/index.ts +++ b/apps/www/__registry__/index.ts @@ -184,6 +184,48 @@ export const Index = { component: () => import('../src/lib/registry/default/example/CardWithForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/CardWithForm.vue'], }, + CarouselApi: { + name: 'CarouselApi', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/default/example/CarouselApi.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/CarouselApi.vue'], + }, + CarouselDemo: { + name: 'CarouselDemo', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/default/example/CarouselDemo.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/CarouselDemo.vue'], + }, + CarouselOrientation: { + name: 'CarouselOrientation', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/default/example/CarouselOrientation.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/CarouselOrientation.vue'], + }, + CarouselPlugin: { + name: 'CarouselPlugin', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/default/example/CarouselPlugin.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/CarouselPlugin.vue'], + }, + CarouselSize: { + name: 'CarouselSize', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/default/example/CarouselSize.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/CarouselSize.vue'], + }, + CarouselSpacing: { + name: 'CarouselSpacing', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/default/example/CarouselSpacing.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/CarouselSpacing.vue'], + }, CheckboxDemo: { name: 'CheckboxDemo', type: 'components:example', @@ -977,6 +1019,48 @@ export const Index = { component: () => import('../src/lib/registry/new-york/example/CardWithForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/CardWithForm.vue'], }, + CarouselApi: { + name: 'CarouselApi', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/new-york/example/CarouselApi.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/CarouselApi.vue'], + }, + CarouselDemo: { + name: 'CarouselDemo', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/new-york/example/CarouselDemo.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/CarouselDemo.vue'], + }, + CarouselOrientation: { + name: 'CarouselOrientation', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/new-york/example/CarouselOrientation.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/CarouselOrientation.vue'], + }, + CarouselPlugin: { + name: 'CarouselPlugin', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/new-york/example/CarouselPlugin.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/CarouselPlugin.vue'], + }, + CarouselSize: { + name: 'CarouselSize', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/new-york/example/CarouselSize.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/CarouselSize.vue'], + }, + CarouselSpacing: { + name: 'CarouselSpacing', + type: 'components:example', + registryDependencies: ['carousel', 'card'], + component: () => import('../src/lib/registry/new-york/example/CarouselSpacing.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/CarouselSpacing.vue'], + }, CheckboxDemo: { name: 'CheckboxDemo', type: 'components:example', diff --git a/apps/www/package.json b/apps/www/package.json index 4d799b54..e4462ead 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -9,9 +9,10 @@ "dev": "vitepress dev", "build": "vitepress build", "preview": "vitepress preview", - "typecheck": "vue-tsc --noEmit", - "typecheck:registry": "vue-tsc --noEmit -p tsconfig.registry.json", - "build:registry": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts" + "typecheck": "vue-tsc", + "typecheck:registry": "vue-tsc -p tsconfig.registry.json", + "build:registry": "tsx ./scripts/build-registry.ts", + "build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts" }, "dependencies": { "@formkit/auto-animate": "^0.8.0", @@ -27,6 +28,9 @@ "clsx": "^2.0.0", "codesandbox": "^2.2.3", "date-fns": "^2.30.0", + "embla-carousel": "8.0.0-rc19", + "embla-carousel-autoplay": "8.0.0-rc19", + "embla-carousel-vue": "8.0.0-rc19", "lucide-vue-next": "^0.276.0", "radix-vue": "^1.2.5", "tailwindcss-animate": "^1.0.7", @@ -47,7 +51,7 @@ "@vitejs/plugin-vue-jsx": "^3.0.2", "@vue/compiler-core": "^3.3.7", "@vue/compiler-dom": "^3.3.7", - "@vue/tsconfig": "^0.4.0", + "@vue/tsconfig": "^0.5.1", "autoprefixer": "^10.4.16", "lodash.template": "^4.5.0", "pathe": "^1.1.1", @@ -59,6 +63,6 @@ "unplugin-icons": "^0.17.1", "vite": "^4.5.0", "vitepress": "^1.0.0-rc.24", - "vue-tsc": "^1.8.25" + "vue-tsc": "^1.8.27" } } diff --git a/apps/www/src/content/docs/components/carousel.md b/apps/www/src/content/docs/components/carousel.md new file mode 100644 index 00000000..c1a34a73 --- /dev/null +++ b/apps/www/src/content/docs/components/carousel.md @@ -0,0 +1,283 @@ +--- +title: Carousel +description: A carousel with motion and swipe built using Embla. +source: apps/www/src/lib/registry/default/ui/carousel +primitive: https://www.embla-carousel.com/api +--- + + + + + +## About + +The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library. + +## Installation + + +```bash +npx shadcn-vue@latest add carousel +``` + +## Usage + +```vue + + + +``` + +## Examples + +### Orientation + +Use the `orientation` prop to set the orientation of the carousel. + + + +```vue + + ... + +``` + +### Sizes + +To set the size of the items, you can use the `basis` utility class on the ``. + + + + +Example + +```vue title="Example" showLineNumbers {4-6} +// 33% of the carousel width. + + + ... + ... + ... + + +``` + + +Responsive + +```vue title="Responsive" showLineNumbers {4-6} +// 50% on small screens and 33% on larger screens. + + + ... + ... + ... + + +``` + +### Spacing + +To set the spacing between the items, we use a `pl-[VALUE]` utility on the `` and a negative `-ml-[VALUE]` on the ``. + + + +**Why:** I tried to use the `gap` property or a `grid` layout on the ` +CarouselContent` but it required a lot of math and mental effort to get the +spacing right. I found `pl-[VALUE]` and `-ml-[VALUE]` utilities much easier to +use. +

+You can always adjust this in your own project if you need to. + +
+ + + +Example + +```vue showLineNumbers /-ml-4/ /pl-4/ + +``` + +Responsive + +```vue showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/ + +``` + + +## Options + +You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information. + +```vue showLineNumbers {3-6} + +``` + +## API + +### Method 1 + +Use the `@init-api` emit method on `` component to set the instance of the API. + + + +### Method 2 + +You can access it through setting a template ref on the `` component. + +```vue showLineNumbers {2,5,9} + + + +``` + +## Events + +You can listen to events using the API. To get the API instance use the `@init-api` emit method on the `` component + +```vue showLineNumbers {5,7-9,25} + + + +``` + +See the [Embla Carousel docs](https://www.embla-carousel.com/api/events/) for more information on using events. + +## Slot Props + +You can get the reactive slot props like `carouselRef, canScrollNext..Prev, scrollNext..Prev` using the `v-slot` directive in the `` component to extend the functionality. + +```vue showLineNumbers {2} + +``` + +## Plugins + +You can use the `plugins` prop to add plugins to the carousel. + +```bash +npm i embla-carousel-autoplay +``` + + +```vue showLineNumbers {2,8-10} + + + +``` + + + +See the [Embla Carousel docs](https://www.embla-carousel.com/api/plugins/) for more information on using plugins. \ No newline at end of file diff --git a/apps/www/src/lib/registry/default/example/CarouselApi.vue b/apps/www/src/lib/registry/default/example/CarouselApi.vue new file mode 100644 index 00000000..0b25dc94 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/CarouselApi.vue @@ -0,0 +1,51 @@ + + + diff --git a/apps/www/src/lib/registry/default/example/CarouselDemo.vue b/apps/www/src/lib/registry/default/example/CarouselDemo.vue new file mode 100644 index 00000000..1fa4f1a3 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/CarouselDemo.vue @@ -0,0 +1,24 @@ + + + diff --git a/apps/www/src/lib/registry/default/example/CarouselOrientation.vue b/apps/www/src/lib/registry/default/example/CarouselOrientation.vue new file mode 100644 index 00000000..7d19c06e --- /dev/null +++ b/apps/www/src/lib/registry/default/example/CarouselOrientation.vue @@ -0,0 +1,30 @@ + + + diff --git a/apps/www/src/lib/registry/default/example/CarouselPlugin.vue b/apps/www/src/lib/registry/default/example/CarouselPlugin.vue new file mode 100644 index 00000000..7c7a3d00 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/CarouselPlugin.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/www/src/lib/registry/default/example/CarouselSize.vue b/apps/www/src/lib/registry/default/example/CarouselSize.vue new file mode 100644 index 00000000..12726784 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/CarouselSize.vue @@ -0,0 +1,29 @@ + + + diff --git a/apps/www/src/lib/registry/default/example/CarouselSpacing.vue b/apps/www/src/lib/registry/default/example/CarouselSpacing.vue new file mode 100644 index 00000000..6d8ee403 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/CarouselSpacing.vue @@ -0,0 +1,29 @@ + + + diff --git a/apps/www/src/lib/registry/default/example/CommandDialogDemo.vue b/apps/www/src/lib/registry/default/example/CommandDialogDemo.vue index 9f18c79a..ddc3f24c 100644 --- a/apps/www/src/lib/registry/default/example/CommandDialogDemo.vue +++ b/apps/www/src/lib/registry/default/example/CommandDialogDemo.vue @@ -14,17 +14,22 @@ import { const open = ref(false) -const keys = useMagicKeys() -const CmdJ = keys['Cmd+J'] +const { Meta_J, Ctrl_J } = useMagicKeys({ + passive: false, + onEventFired(e) { + if (e.key === 'j' && (e.metaKey || e.ctrlKey)) + e.preventDefault() + }, +}) + +watch([Meta_J, Ctrl_J], (v) => { + if (v[0] || v[1]) + handleOpenChange() +}) function handleOpenChange() { open.value = !open.value } - -watch(CmdJ, (v) => { - if (v) - handleOpenChange() -})