Merge branch 'dev' of into refactor/use-class-prop

This commit is contained in:
Sadegh Barati 2024-01-19 23:13:26 +03:30
commit ec233cbf5b
92 changed files with 3036 additions and 1329 deletions

View File

@ -11,17 +11,50 @@ on:
- dev - dev
paths: paths:
- 'apps/www/**' - 'apps/www/**'
pull_request_target:
types:
# When a created pull request from forked repo, it will be comment 'Should deploy to add label'
- opened
# When a labeled '🚀request-deploy' pull request from forked repo, it will be deploy to Cloudflare Pages
- labeled
paths:
- 'apps/www/**'
# Allows you to run this workflow manually from the Actions tab
# eslint-disable-next-line yml/no-empty-mapping-value
workflow_dispatch:
permissions:
# default contents: read & write (in forked repos, only read)
contents: write
# default deployments: read & write (in forked repos, only read)
deployments: write
# default pull-requests: read & write (in forked repos, only read)
pull-requests: write
jobs: jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Publish to Cloudflare Pages name: Publish to Cloudflare Pages
# push event in main branch
# workflow_dispatch event
# pull_request event from not forked repo
# pull_request_target event with label "🚀request-deploy" from forked repo
if: ${{
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) ||
(github.event_name == 'pull_request_target' &&
github.event.action == 'labeled' &&
github.event.pull_request.head.repo.fork == true &&
contains(github.event.label.name, '🚀request-deploy'))
}}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
fetch-depth: 0
# Run a build step here # Run a build step here
- name: Setup Node.js environment - name: Setup Node.js environment
@ -56,7 +89,7 @@ jobs:
# Run a action to publish docs # Run a action to publish docs
- name: Publish to Cloudflare Pages - name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1.5.0 uses: zernonia/cloudflare-pages-action@v0.0.7
with: with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
@ -66,7 +99,20 @@ jobs:
gitHubToken: ${{ secrets.GITHUB_TOKEN }} gitHubToken: ${{ secrets.GITHUB_TOKEN }}
# Optional: Switch what branch you are publishing to. # Optional: Switch what branch you are publishing to.
# By default this will be the branch which triggered this workflow # By default this will be the branch which triggered this workflow
# branch: main branch: ${{ github.ref == 'refs/heads/dev' && 'dev' || format('refs/pull/{0}/merge', github.event.number) }}
# Optional: Change the working directory # Optional: Change the working directory
workingDirectory: apps/www workingDirectory: apps/www
wranglerVersion: '3' wranglerVersion: '3'
- name: Remove label
if: ${{ github.event_name == 'pull_request_target' && contains(github.event.label.name, '🚀request-deploy') }}
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: ['🚀request-deploy']
})

View File

@ -3,9 +3,17 @@ import { defineConfig } from 'vitepress'
import Icons from 'unplugin-icons/vite' import Icons from 'unplugin-icons/vite'
import tailwind from 'tailwindcss' import tailwind from 'tailwindcss'
import autoprefixer from 'autoprefixer' import autoprefixer from 'autoprefixer'
import { createCssVariablesTheme } from 'shikiji'
import { siteConfig } from './theme/config/site' import { siteConfig } from './theme/config/site'
import ComponentPreviewPlugin from './theme/plugins/previewer' import ComponentPreviewPlugin from './theme/plugins/previewer'
const cssVariables = createCssVariablesTheme({
name: 'css-variables',
variablePrefix: '--shiki-',
variableDefaults: {},
fontStyle: true,
})
// https://vitepress.dev/reference/site-config // https://vitepress.dev/reference/site-config
export default defineConfig({ export default defineConfig({
title: siteConfig.name, title: siteConfig.name,
@ -50,7 +58,7 @@ export default defineConfig({
srcDir: path.resolve(__dirname, '../src'), srcDir: path.resolve(__dirname, '../src'),
markdown: { markdown: {
theme: 'css-variables', theme: cssVariables,
config(md) { config(md) {
md.use(ComponentPreviewPlugin) md.use(ComponentPreviewPlugin)
}, },

View File

@ -52,7 +52,7 @@ const { style } = useConfigStore()
</div> </div>
</div> </div>
<div <div
:class="cn('preview flex min-h-[350px] w-full justify-center p-6 lg:p-10', { :class="cn('preview flex min-h-[350px] w-full justify-center p-10 items-center', {
'items-center': align === 'center', 'items-center': align === 'center',
'items-start': align === 'start', 'items-start': align === 'start',
'items-end': align === 'end', 'items-end': align === 'end',

View File

@ -330,6 +330,11 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/toggle', href: '/docs/components/toggle',
items: [], items: [],
}, },
{
title: 'Toggle Group',
href: '/docs/components/toggle-group',
items: [],
},
{ {
title: 'Tooltip', title: 'Tooltip',
href: '/docs/components/tooltip', href: '/docs/components/tooltip',

View File

@ -1,5 +1,5 @@
:root { :root {
--shiki-color-text: #EEEEEE; --shiki-foreground: #EEEEEE;
--shiki-color-background: #ffffff; --shiki-color-background: #ffffff;
--shiki-token-constant: #ffffff; --shiki-token-constant: #ffffff;
--shiki-token-string: #ffffff88; --shiki-token-string: #ffffff88;

View File

@ -359,6 +359,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/DatePickerWithRange.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/DatePickerWithRange.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/DatePickerWithRange.vue'], files: ['../src/lib/registry/default/example/DatePickerWithRange.vue'],
}, },
DateTimePickerDemo: {
name: 'DateTimePickerDemo',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/default/example/DateTimePickerDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/DateTimePickerDemo.vue'],
},
DialogCustomCloseButton: { DialogCustomCloseButton: {
name: 'DialogCustomCloseButton', name: 'DialogCustomCloseButton',
type: 'components:example', type: 'components:example',
@ -499,6 +506,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/RadioGroupForm.vue'], files: ['../src/lib/registry/default/example/RadioGroupForm.vue'],
}, },
RangePickerWithSlot: {
name: 'RangePickerWithSlot',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/default/example/RangePickerWithSlot.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/RangePickerWithSlot.vue'],
},
ScrollAreaDemo: { ScrollAreaDemo: {
name: 'ScrollAreaDemo', name: 'ScrollAreaDemo',
type: 'components:example', type: 'components:example',
@ -681,6 +695,48 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/ToggleDisabledDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/ToggleDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleDisabledDemo.vue'], files: ['../src/lib/registry/default/example/ToggleDisabledDemo.vue'],
}, },
ToggleGroupDemo: {
name: 'ToggleGroupDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupDemo.vue'],
},
ToggleGroupDisabledDemo: {
name: 'ToggleGroupDisabledDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupDisabledDemo.vue'],
},
ToggleGroupLargeDemo: {
name: 'ToggleGroupLargeDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupLargeDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupLargeDemo.vue'],
},
ToggleGroupOutlineDemo: {
name: 'ToggleGroupOutlineDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupOutlineDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupOutlineDemo.vue'],
},
ToggleGroupSingleDemo: {
name: 'ToggleGroupSingleDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupSingleDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupSingleDemo.vue'],
},
ToggleGroupSmallDemo: {
name: 'ToggleGroupSmallDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupSmallDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupSmallDemo.vue'],
},
ToggleItalicDemo: { ToggleItalicDemo: {
name: 'ToggleItalicDemo', name: 'ToggleItalicDemo',
type: 'components:example', type: 'components:example',
@ -1194,6 +1250,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/DatePickerWithRange.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/DatePickerWithRange.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/DatePickerWithRange.vue'], files: ['../src/lib/registry/new-york/example/DatePickerWithRange.vue'],
}, },
DateTimePickerDemo: {
name: 'DateTimePickerDemo',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/new-york/example/DateTimePickerDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/DateTimePickerDemo.vue'],
},
DialogCustomCloseButton: { DialogCustomCloseButton: {
name: 'DialogCustomCloseButton', name: 'DialogCustomCloseButton',
type: 'components:example', type: 'components:example',
@ -1334,6 +1397,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'], files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'],
}, },
RangePickerWithSlot: {
name: 'RangePickerWithSlot',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/new-york/example/RangePickerWithSlot.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/RangePickerWithSlot.vue'],
},
ScrollAreaDemo: { ScrollAreaDemo: {
name: 'ScrollAreaDemo', name: 'ScrollAreaDemo',
type: 'components:example', type: 'components:example',
@ -1516,6 +1586,48 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/ToggleDisabledDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/ToggleDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleDisabledDemo.vue'], files: ['../src/lib/registry/new-york/example/ToggleDisabledDemo.vue'],
}, },
ToggleGroupDemo: {
name: 'ToggleGroupDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupDemo.vue'],
},
ToggleGroupDisabledDemo: {
name: 'ToggleGroupDisabledDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupDisabledDemo.vue'],
},
ToggleGroupLargeDemo: {
name: 'ToggleGroupLargeDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupLargeDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupLargeDemo.vue'],
},
ToggleGroupOutlineDemo: {
name: 'ToggleGroupOutlineDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupOutlineDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupOutlineDemo.vue'],
},
ToggleGroupSingleDemo: {
name: 'ToggleGroupSingleDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupSingleDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupSingleDemo.vue'],
},
ToggleGroupSmallDemo: {
name: 'ToggleGroupSmallDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupSmallDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupSmallDemo.vue'],
},
ToggleItalicDemo: { ToggleItalicDemo: {
name: 'ToggleItalicDemo', name: 'ToggleItalicDemo',
type: 'components:example', type: 'components:example',

View File

@ -1,7 +1,7 @@
{ {
"name": "www", "name": "www",
"type": "module", "type": "module",
"version": "0.8.6", "version": "0.8.7",
"files": [ "files": [
"dist" "dist"
], ],
@ -15,28 +15,28 @@
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts" "build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts"
}, },
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.0", "@formkit/auto-animate": "^0.8.1",
"@morev/vue-transitions": "^2.3.6", "@morev/vue-transitions": "^2.3.6",
"@radix-icons/vue": "^1.0.0", "@radix-icons/vue": "^1.0.0",
"@stackblitz/sdk": "^1.9.0", "@stackblitz/sdk": "^1.9.0",
"@tanstack/vue-table": "^8.10.7", "@tanstack/vue-table": "^8.11.6",
"@unovis/ts": "^1.2.3", "@unovis/ts": "^1.3.1",
"@unovis/vue": "1.3.0-beta.3", "@unovis/vue": "^1.3.1",
"@vee-validate/zod": "^4.12.3", "@vee-validate/zod": "^4.12.4",
"@vueuse/core": "^10.5.0", "@vueuse/core": "^10.7.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.1.0",
"codesandbox": "^2.2.3", "codesandbox": "^2.2.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"embla-carousel": "8.0.0-rc19", "embla-carousel": "8.0.0-rc19",
"embla-carousel-autoplay": "8.0.0-rc19", "embla-carousel-autoplay": "8.0.0-rc19",
"embla-carousel-vue": "8.0.0-rc19", "embla-carousel-vue": "8.0.0-rc19",
"lucide-vue-next": "^0.276.0", "lucide-vue-next": "^0.276.0",
"radix-vue": "^1.2.5", "radix-vue": "^1.3.2",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"v-calendar": "^3.1.2", "v-calendar": "^3.1.2",
"vee-validate": "4.12.3", "vee-validate": "4.12.4",
"vue": "^3.3.7", "vue": "^3.4.14",
"vue-wrap-balancer": "^1.1.3", "vue-wrap-balancer": "^1.1.3",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
@ -47,22 +47,23 @@
"@iconify/vue": "^4.1.1", "@iconify/vue": "^4.1.1",
"@types/lodash.template": "^4.5.2", "@types/lodash.template": "^4.5.2",
"@types/node": "^20.8.10", "@types/node": "^20.8.10",
"@vitejs/plugin-vue": "^4.4.0", "@vitejs/plugin-vue": "^5.0.3",
"@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-core": "^3.3.7", "@vue/compiler-core": "^3.4.14",
"@vue/compiler-dom": "^3.3.7", "@vue/compiler-dom": "^3.4.14",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"pathe": "^1.1.1", "pathe": "^1.1.2",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"tailwind-merge": "^2.0.0", "shikiji": "^0.10.0-beta.2",
"tailwindcss": "^3.3.5", "tailwind-merge": "^2.2.0",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.0", "tsx": "^4.7.0",
"typescript": "^5.2.2", "typescript": "^5.3.3",
"unplugin-icons": "^0.17.1", "unplugin-icons": "^0.17.1",
"vite": "^4.5.0", "vite": "^5.0.11",
"vitepress": "^1.0.0-rc.24", "vitepress": "^1.0.0-rc.37",
"vue-tsc": "^1.8.27" "vue-tsc": "^1.8.27"
} }
} }

View File

@ -5,7 +5,7 @@ description: Powered by amazing open source projects.
## About ## About
[shadcn-vue](https://shadcn-vuee.com) is a port of [shadcn/ui](https://ui.shadcn.com) for Vue/Nuxt. It's maintained by [radix-vue](https://github.com/radix-vue). [shadcn-vue](https://shadcn-vue.com) is a port of [shadcn/ui](https://ui.shadcn.com) for Vue/Nuxt. It's maintained by [radix-vue](https://github.com/radix-vue).
## Credits ## Credits
@ -17,4 +17,4 @@ description: Powered by amazing open source projects.
## License ## License
MIT © [shadcn](https://shadcn.com) & [radix-vue](https://github.com/radix-vue) MIT © [shadcn](https://shadcn.com) & [radix-vue](https://github.com/radix-vue)

View File

@ -56,5 +56,40 @@ import { Calendar } from '@/components/ui/calendar'
</template> </template>
``` ```
See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information. The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
### Slots
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
- `day-content`
- `day-popover`
- `dp-footer`
- `footer`
- `header-title-wrapper`
- `header-title`
- `header-prev-button`
- `header-next-button`
- `nav`
- `nav-prev-button`
- `nav-next-button`
- `page`
- `time-header`
Example using the `day-content` slot:
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/calendar'
</script>
<template>
<Calendar>
<template #day-content="{ day, dayProps, dayEvents }">
<div v-bind="dayProps" v-on="dayEvents">
{{ day.label }}
</div>
</template>
</Calendar>
</template>
```

View File

@ -48,18 +48,6 @@ import {
## Examples ## Examples
### Orientation
Use the `orientation` prop to set the orientation of the carousel.
<ComponentPreview name="CarouselOrientation" />
```vue
<Carousel orientation="vertical | horizontal">
...
</Carousel>
```
### Sizes ### Sizes
To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`. To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`.
@ -151,6 +139,17 @@ Responsive
</template> </template>
``` ```
### Orientation
Use the `orientation` prop to set the orientation of the carousel.
<ComponentPreview name="CarouselOrientation" />
```vue
<Carousel orientation="vertical | horizontal">
...
</Carousel>
```
## Options ## Options
@ -280,4 +279,4 @@ import Autoplay from 'embla-carousel-autoplay'
<ComponentPreview name="CarouselPlugin" /> <ComponentPreview name="CarouselPlugin" />
See the [Embla Carousel docs](https://www.embla-carousel.com/api/plugins/) for more information on using plugins. See the [Embla Carousel docs](https://www.embla-carousel.com/api/plugins/) for more information on using plugins.

View File

@ -64,10 +64,18 @@ const date = ref<Date>()
<ComponentPreview name="DatePickerWithRange" /> <ComponentPreview name="DatePickerWithRange" />
### Date Time Picker
<ComponentPreview name="DateTimePickerDemo" />
### With Presets ### With Presets
<ComponentPreview name="DatePickerWithPresets" /> <ComponentPreview name="DatePickerWithPresets" />
### With Slot
<ComponentPreview name="RangePickerWithSlot" />
### Form ### Form
<ComponentPreview name="DatePickerForm" /> <ComponentPreview name="DatePickerForm" />

View File

@ -173,7 +173,7 @@ const formSchema = toTypedSchema(z.object({
### Define a form ### Define a form
Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a from. Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a form.
<TabPreview name="Composition" :names="['Composition', 'Component']"> <TabPreview name="Composition" :names="['Composition', 'Component']">

View File

@ -0,0 +1,93 @@
---
title: Toggle Group
description: A set of two-state buttons that can be toggled on or off.
source: apps/www/src/lib/registry/default/ui/toggle-group
primitive: https://www.radix-vue.com/components/toggle-group.html
---
<ComponentPreview name="ToggleGroupDemo" />
## Installation
<TabPreview name="CLI">
<template #CLI>
```bash
npx shadcn-vue@latest add toggle-group
```
</template>
<template #Manual>
<Steps>
### Install the following dependencies:
```bash
npm install radix-vue
```
### Copy and paste the following code into your project
<<< @/lib/registry/default/ui/toggle-group/ToggleGroup.vue
</Steps>
</template>
</TabPreview>
## Usage
```vue
<script setup lang="ts">
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
</script>
<template>
<ToggleGroup type="single">
<ToggleGroupItem value="a">
A
</ToggleGroupItem>
<ToggleGroupItem value="b">
B
</ToggleGroupItem>
<ToggleGroupItem value="c">
C
</ToggleGroupItem>
</ToggleGroup>
</template>
```
## Examples
### Default
<ComponentPreview name="ToggleGroupDemo" />
### Outline
<ComponentPreview name="ToggleGroupOutlineDemo" />
### Single
<ComponentPreview name="ToggleGroupSingleDemo" />
### Small
<ComponentPreview name="ToggleGroupSmallDemo" />
### Large
<ComponentPreview name="ToggleGroupLargeDemo" />
### Disabled
<ComponentPreview name="ToggleGroupDisabledDemo" />

View File

@ -19,13 +19,63 @@ npm create vite@latest my-vue-app -- --template vue-ts
### Add Tailwind and its configuration ### Add Tailwind and its configuration
Install `tailwindcss` and its peer dependencies, then generate your `tailwind.config.js` and `postcss.config.js` files: Install `tailwindcss` and its peer dependencies, then generate your `tailwind.config.js` and configure `postcss` plugins
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
``` <TabsMarkdown>
<TabMarkdown title="vite.config.ts">
Vite already has [`postcss`](https://github.com/vitejs/vite/blob/main/packages/vite/package.json#L78) dependency so you don't have to install it again in your package.json
```bash
npm install -D tailwindcss autoprefixer
```
#### `vite.config`
```typescript {5,6,10-14}
import path from "path"
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import tailwind from "tailwindcss"
import autoprefixer from "autoprefixer"
export default defineConfig({
plugins: [vue()],
css: {
postcss: {
plugins: [tailwind(), autoprefixer()],
},
},
resolve: {...}
})
```
</TabMarkdown>
<TabMarkdown title="postcss.config.js">
```bash
npm install -D tailwindcss autoprefixer postcss
```
#### `postcss.config.js`
```js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
```
</TabMarkdown>
</TabsMarkdown>
### Edit tsconfig.json ### Edit tsconfig.json
@ -42,6 +92,11 @@ Add the code below to the compilerOptions of your tsconfig.json so your app can
Add the code below to the vite.config.ts so your app can resolve paths without error Add the code below to the vite.config.ts so your app can resolve paths without error
```bash
# (so you can import "path" without error)
npm i -D @types/node
```
```typescript ```typescript
import path from "path" import path from "path"
import vue from "@vitejs/plugin-vue" import vue from "@vitejs/plugin-vue"

View File

@ -27,8 +27,8 @@ watchOnce(api, (api) => {
</script> </script>
<template> <template>
<div class="flex flex-col items-center space-x-2"> <div class="w-full sm:w-auto">
<Carousel class="w-full max-w-xs" @init-api="setApi"> <Carousel class="relative w-full max-w-xs" @init-api="setApi">
<CarouselContent> <CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index"> <CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1"> <div class="p-1">

View File

@ -4,21 +4,19 @@ import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel class="relative w-full max-w-xs">
<Carousel class="w-full max-w-xs"> <CarouselContent>
<CarouselContent> <CarouselItem v-for="(_, index) in 5" :key="index">
<CarouselItem v-for="(_, index) in 5" :key="index"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-4xl font-semibold">{{ index + 1 }}</span>
<span class="text-4xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -4,27 +4,25 @@ import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script> </script>
<template> <template>
<div class="w-1/2"> <Carousel
<Carousel orientation="vertical"
orientation="vertical" class="relative w-full max-w-xsw-full max-w-xs"
class="w-full max-w-xs" :opts="{
:opts="{ align: 'start',
align: 'start', }"
}" >
> <CarouselContent class="-mt-1 h-[200px]">
<CarouselContent class="-mt-1 h-[200px]"> <CarouselItem v-for="(_, index) in 5" :key="index" class="p-1 md:basis-1/2">
<CarouselItem v-for="(_, index) in 5" :key="index" class="p-1 md:basis-1/2"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex items-center justify-center p-6">
<CardContent class="flex items-center justify-center p-6"> <span class="text-3xl font-semibold">{{ index + 1 }}</span>
<span class="text-3xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -11,26 +11,24 @@ const plugin = Autoplay({
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel
<Carousel class="relative w-full max-w-xs"
class="w-full max-w-xs" :plugins="[plugin]"
:plugins="[plugin]" @mouseenter="plugin.stop"
@mouseenter="plugin.stop" @mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];"
@mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];" >
> <CarouselContent>
<CarouselContent> <CarouselItem v-for="(_, index) in 5" :key="index">
<CarouselItem v-for="(_, index) in 5" :key="index"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-4xl font-semibold">{{ index + 1 }}</span>
<span class="text-4xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -4,26 +4,24 @@ import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel
<Carousel class="relative w-full max-w-sm"
class="w-full max-w-xs" :opts="{
:opts="{ align: 'start',
align: 'start', }"
}" >
> <CarouselContent>
<CarouselContent> <CarouselItem v-for="(_, index) in 5" :key="index" class="md:basis-1/2 lg:basis-1/3">
<CarouselItem v-for="(_, index) in 5" :key="index" class="md:basis-1/2 lg:basis-1/3"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-3xl font-semibold">{{ index + 1 }}</span>
<span class="text-3xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -4,26 +4,24 @@ import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel
<Carousel class="relative w-full max-w-sm"
class="w-full max-w-sm" :opts="{
:opts="{ align: 'start',
align: 'start', }"
}" >
> <CarouselContent class="-ml-1">
<CarouselContent class="-ml-1"> <CarouselItem v-for="(_, index) in 5" :key="index" class="pl-1 md:basis-1/2 lg:basis-1/3">
<CarouselItem v-for="(_, index) in 5" :key="index" class="pl-1 md:basis-1/2 lg:basis-1/3"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-2xl font-semibold">{{ index + 1 }}</span>
<span class="text-2xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import { format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
const date = ref<Date>()
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
:variant="'outline'"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>{{ date ? format(date, 'PPP - hh:mm') : "Pick a date" }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" mode="datetime" />
</PopoverContent>
</Popover>
</template>

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import { addDays, format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
const date = ref({
start: new Date(2022, 0, 20),
end: addDays(new Date(2022, 0, 20), 20),
})
</script>
<template>
<div :class="cn('grid gap-2', $attrs.class ?? '')">
<Popover>
<PopoverTrigger as-child>
<Button
id="date"
:variant="'outline'"
:class="cn(
'w-[300px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>
{{ date.start ? (
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
: format(date.start, 'LLL dd, y')
) : 'Pick a date' }}
</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" align="start" :avoid-collisions="true">
<Calendar
v-model.range="date"
mode="date"
:columns="2"
>
<template #footer>
<div class="w-full px-3 pb-3">
Entry time
<Calendar
v-model="date.start"
mode="time"
hide-time-header
/>
Exit time
<Calendar
v-model="date.end"
mode="time"
hide-time-header
/>
</div>
</template>
</Calendar>
</PopoverContent>
</Popover>
</div>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" disabled>
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" size="lg">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" variant="outline">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="single">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" size="sm">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -1 +0,0 @@
export { default as AccordionDemo } from './AccordionDemo.vue'

View File

@ -3,7 +3,8 @@ import { useVModel } from '@vueuse/core'
import type { Calendar } from 'v-calendar' import type { Calendar } from 'v-calendar'
import { DatePicker } from 'v-calendar' import { DatePicker } from 'v-calendar'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next' import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { computed, nextTick, onMounted, ref } from 'vue' import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
import { isVCalendarSlot } from '.'
import { buttonVariants } from '@/lib/registry/default/ui/button' import { buttonVariants } from '@/lib/registry/default/ui/button'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -63,11 +64,21 @@ onMounted(async () => {
if (modelValue.value instanceof Date && calendarRef.value) if (modelValue.value instanceof Date && calendarRef.value)
calendarRef.value.focusDate(modelValue.value) calendarRef.value.focusDate(modelValue.value)
}) })
const $slots = useSlots()
const vCalendarSlots = computed(() => {
return Object.keys($slots)
.filter(name => isVCalendarSlot(name))
.reduce((obj: Record<string, any>, key: string) => {
obj[key] = $slots[key]
return obj
}, {})
})
</script> </script>
<template> <template>
<div class="relative"> <div class="relative">
<div class="absolute flex justify-between w-full px-4 top-3 z-[1]"> <div v-if="$attrs.mode !== 'time'" class="absolute flex justify-between w-full px-4 top-3 z-[1]">
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('prev')"> <button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('prev')">
<ChevronLeft class="w-4 h-4" /> <ChevronLeft class="w-4 h-4" />
</button> </button>
@ -85,7 +96,11 @@ onMounted(async () => {
trim-weeks trim-weeks
:transition="'none'" :transition="'none'"
:columns="columns" :columns="columns"
/> >
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</DatePicker>
</div> </div>
</template> </template>
@ -242,4 +257,49 @@ onMounted(async () => {
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate))); -webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
transform: translateY(calc(-1 * var(--vc-slide-translate))); transform: translateY(calc(-1 * var(--vc-slide-translate)));
} }
/**
* Timepicker styles
*/
.vc-time-picker {
@apply flex flex-col items-center p-2;
}
.vc-time-picker.vc-invalid {
@apply pointer-events-none opacity-50;
}
.vc-time-picker.vc-attached {
@apply border-t border-solid border-secondary mt-2;
}
.vc-time-picker > * + * {
@apply mt-1;
}
.vc-time-header {
@apply flex items-center text-sm font-semibold uppercase mt-1 px-1 leading-6;
}
.vc-time-select-group {
@apply inline-flex items-center px-1 rounded-md bg-primary-foreground border border-solid border-secondary;
}
.vc-time-select-group .vc-base-icon {
@apply mr-1 text-primary stroke-primary;
}
.vc-time-select-group select {
@apply bg-primary-foreground p-1 appearance-none outline-none text-center;
}
.vc-time-weekday {
@apply text-muted-foreground tracking-wide;
}
.vc-time-month {
@apply text-primary ml-2;
}
.vc-time-day {
@apply text-primary ml-1;
}
.vc-time-year {
@apply text-muted-foreground ml-2;
}
.vc-time-colon {
@apply mb-0.5;
}
.vc-time-decimal {
@apply ml-0.5;
}
</style> </style>

View File

@ -1 +1,22 @@
export { default as Calendar } from './Calendar.vue' export { default as Calendar } from './Calendar.vue'
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
const validSlots: CalendarSlotName[] = [
'day-content',
'day-popover',
'dp-footer',
'footer',
'header-title-wrapper',
'header-title',
'header-prev-button',
'header-next-button',
'nav',
'nav-prev-button',
'nav-next-button',
'page',
'time-header',
]
return validSlots.includes(slotName as CalendarSlotName)
}

View File

@ -14,7 +14,7 @@ const { orientation, canScrollNext, scrollNext } = useCarousel()
<Button <Button
:disabled="!canScrollNext" :disabled="!canScrollNext"
:class="cn( :class="cn(
'absolute h-10 w-10 rounded-full p-0', 'touch-manipulation absolute h-8 w-8 rounded-full p-0',
orientation === 'horizontal' orientation === 'horizontal'
? '-right-12 top-1/2 -translate-y-1/2' ? '-right-12 top-1/2 -translate-y-1/2'
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90', : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',

View File

@ -14,7 +14,7 @@ const { orientation, canScrollPrev, scrollPrev } = useCarousel()
<Button <Button
:disabled="!canScrollPrev" :disabled="!canScrollPrev"
:class="cn( :class="cn(
'absolute h-10 w-10 rounded-full p-0', 'touch-manipulation absolute h-8 w-8 rounded-full p-0',
orientation === 'horizontal' orientation === 'horizontal'
? '-left-12 top-1/2 -translate-y-1/2' ? '-left-12 top-1/2 -translate-y-1/2'
: '-top-12 left-1/2 -translate-x-1/2 rotate-90', : '-top-12 left-1/2 -translate-x-1/2 rotate-90',

View File

@ -19,6 +19,7 @@ const props = withDefaults(
defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(), { defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(), {
position: 'popper', position: 'popper',
sideOffset: 4, sideOffset: 4,
avoidCollisions: true,
}, },
) )
const emits = defineEmits<SelectContentEmits>() const emits = defineEmits<SelectContentEmits>()

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { type HTMLAttributes, computed, provide } from 'vue'
import { ToggleGroupRoot, type ToggleGroupRootEmits, type ToggleGroupRootProps, useForwardPropsEmits } from 'radix-vue'
import type { toggleVariants } from '@/lib/registry/default/ui/toggle'
import { cn } from '@/lib/utils'
type ToggleGroupVariants = VariantProps<typeof toggleVariants>
const props = defineProps<ToggleGroupRootProps & {
class?: HTMLAttributes['class']
variant?: ToggleGroupVariants['variant']
size?: ToggleGroupVariants['size']
}>()
const emits = defineEmits<ToggleGroupRootEmits>()
provide('toggleGroup', {
variant: props.variant,
size: props.size,
})
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps.value, emits)
</script>
<template>
<ToggleGroupRoot v-bind="forwarded" :class="cn('flex items-center justify-center gap-1', props.class)">
<slot />
</ToggleGroupRoot>
</template>

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { type HTMLAttributes, computed, inject } from 'vue'
import { ToggleGroupItem, type ToggleGroupItemProps, useForwardProps } from 'radix-vue'
import { toggleVariants } from '@/lib/registry/default/ui/toggle'
import { cn } from '@/lib/utils'
type ToggleGroupVariants = VariantProps<typeof toggleVariants>
const props = defineProps<ToggleGroupItemProps & {
class?: HTMLAttributes['class']
variant?: ToggleGroupVariants['variant']
size?: ToggleGroupVariants['size']
}>()
const context = inject<ToggleGroupVariants>('toggleGroup')
const delegatedProps = computed(() => {
const { class: _, variant, size, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps.value)
</script>
<template>
<ToggleGroupItem
v-bind="forwardedProps" :class="cn(toggleVariants({
variant: context?.variant || variant,
size: context?.size || size,
}), props.class)"
>
<slot />
</ToggleGroupItem>
</template>

View File

@ -0,0 +1,2 @@
export { default as ToggleGroup } from './ToggleGroup.vue'
export { default as ToggleGroupItem } from './ToggleGroupItem.vue'

View File

@ -27,8 +27,8 @@ watchOnce(api, (api) => {
</script> </script>
<template> <template>
<div class="flex flex-col items-center space-x-2"> <div class="w-full sm:w-auto">
<Carousel class="w-full max-w-xs" @init-api="setApi"> <Carousel class="relative w-full max-w-xs" @init-api="setApi">
<CarouselContent> <CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index"> <CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1"> <div class="p-1">

View File

@ -4,21 +4,19 @@ import { Card, CardContent } from '@/lib/registry/new-york/ui/card'
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel class="relative w-full max-w-xs">
<Carousel class="w-full max-w-xs"> <CarouselContent>
<CarouselContent> <CarouselItem v-for="(_, index) in 5" :key="index">
<CarouselItem v-for="(_, index) in 5" :key="index"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-4xl font-semibold">{{ index + 1 }}</span>
<span class="text-4xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -4,27 +4,25 @@ import { Card, CardContent } from '@/lib/registry/new-york/ui/card'
</script> </script>
<template> <template>
<div class="w-1/2"> <Carousel
<Carousel orientation="vertical"
orientation="vertical" class="relative w-full max-w-xs"
class="w-full max-w-xs" :opts="{
:opts="{ align: 'start',
align: 'start', }"
}" >
> <CarouselContent class="-mt-1 h-[200px]">
<CarouselContent class="-mt-1 h-[200px]"> <CarouselItem v-for="(_, index) in 5" :key="index" class="p-1 md:basis-1/2">
<CarouselItem v-for="(_, index) in 5" :key="index" class="p-1 md:basis-1/2"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex items-center justify-center p-6">
<CardContent class="flex items-center justify-center p-6"> <span class="text-3xl font-semibold">{{ index + 1 }}</span>
<span class="text-3xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -11,26 +11,24 @@ const plugin = Autoplay({
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel
<Carousel class="relative w-full max-w-xs"
class="w-full max-w-xs" :plugins="[plugin]"
:plugins="[plugin]" @mouseenter="plugin.stop"
@mouseenter="plugin.stop" @mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];"
@mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];" >
> <CarouselContent>
<CarouselContent> <CarouselItem v-for="(_, index) in 5" :key="index">
<CarouselItem v-for="(_, index) in 5" :key="index"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-4xl font-semibold">{{ index + 1 }}</span>
<span class="text-4xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -4,26 +4,24 @@ import { Card, CardContent } from '@/lib/registry/new-york/ui/card'
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel
<Carousel class="relative w-full max-w-xs"
class="w-full max-w-xs" :opts="{
:opts="{ align: 'start',
align: 'start', }"
}" >
> <CarouselContent>
<CarouselContent> <CarouselItem v-for="(_, index) in 5" :key="index" class="md:basis-1/2 lg:basis-1/3">
<CarouselItem v-for="(_, index) in 5" :key="index" class="md:basis-1/2 lg:basis-1/3"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-3xl font-semibold">{{ index + 1 }}</span>
<span class="text-3xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -4,26 +4,24 @@ import { Card, CardContent } from '@/lib/registry/new-york/ui/card'
</script> </script>
<template> <template>
<div class="flex items-center space-x-2"> <Carousel
<Carousel class="w-full max-w-sm"
class="w-full max-w-sm" :opts="{
:opts="{ align: 'start',
align: 'start', }"
}" >
> <CarouselContent class="-ml-1">
<CarouselContent class="-ml-1"> <CarouselItem v-for="(_, index) in 5" :key="index" class="pl-1 md:basis-1/2 lg:basis-1/3">
<CarouselItem v-for="(_, index) in 5" :key="index" class="pl-1 md:basis-1/2 lg:basis-1/3"> <div class="p-1">
<div class="p-1"> <Card>
<Card> <CardContent class="flex aspect-square items-center justify-center p-6">
<CardContent class="flex aspect-square items-center justify-center p-6"> <span class="text-2xl font-semibold">{{ index + 1 }}</span>
<span class="text-2xl font-semibold">{{ index + 1 }}</span> </CardContent>
</CardContent> </Card>
</Card> </div>
</div> </CarouselItem>
</CarouselItem> </CarouselContent>
</CarouselContent> <CarouselPrevious />
<CarouselPrevious /> <CarouselNext />
<CarouselNext /> </Carousel>
</Carousel>
</div>
</template> </template>

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import { format } from 'date-fns'
import { ref } from 'vue'
import { CalendarIcon } from '@radix-icons/vue'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/new-york/ui/popover'
const date = ref<Date>()
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
:variant="'outline'"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>{{ date ? format(date, 'PPP - hh:mm') : "Pick a date" }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" mode="datetime" />
</PopoverContent>
</Popover>
</template>

View File

@ -0,0 +1,73 @@
<script setup lang="ts">
import { addDays, format } from 'date-fns'
import { ref } from 'vue'
import { CalendarIcon } from '@radix-icons/vue'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/new-york/ui/popover'
const date = ref({
start: new Date(2022, 0, 20),
end: addDays(new Date(2022, 0, 20), 20),
})
</script>
<template>
<div :class="cn('grid gap-2', $attrs.class ?? '')">
<Popover>
<PopoverTrigger as-child>
<Button
id="date"
:variant="'outline'"
:class="cn(
'w-[300px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>
{{ date.start ? (
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
: format(date.start, 'LLL dd, y')
) : 'Pick a date' }}
</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" align="start" :avoid-collisions="true">
<Calendar
v-model.range="date"
mode="date"
:columns="2"
>
<template #footer>
<div class="flex w-full mt-6 border-t border-accent pt-4">
<div class="w-1/2">
<strong>Entry time</strong>
<Calendar
v-model="date.start"
mode="time"
hide-time-header
/>
</div>
<div class="w-1/2">
<strong>Exit time</strong>
<Calendar
v-model="date.end"
mode="time"
hide-time-header
/>
</div>
</div>
</template>
</Calendar>
</PopoverContent>
</Popover>
</div>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import {
FontBoldIcon,
FontItalicIcon,
UnderlineIcon,
} from '@radix-icons/vue'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/new-york/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<FontBoldIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<FontItalicIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<UnderlineIcon class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import {
FontBoldIcon,
FontItalicIcon,
UnderlineIcon,
} from '@radix-icons/vue'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/new-york/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" disabled>
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<FontBoldIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<FontItalicIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<UnderlineIcon class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import {
FontBoldIcon,
FontItalicIcon,
UnderlineIcon,
} from '@radix-icons/vue'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/new-york/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" size="lg">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<FontBoldIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<FontItalicIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<UnderlineIcon class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import {
FontBoldIcon,
FontItalicIcon,
UnderlineIcon,
} from '@radix-icons/vue'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/new-york/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" variant="outline">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<FontBoldIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<FontItalicIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<UnderlineIcon class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import {
FontBoldIcon,
FontItalicIcon,
UnderlineIcon,
} from '@radix-icons/vue'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/new-york/ui/toggle-group'
</script>
<template>
<ToggleGroup type="single">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<FontBoldIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<FontItalicIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<UnderlineIcon class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import {
FontBoldIcon,
FontItalicIcon,
UnderlineIcon,
} from '@radix-icons/vue'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/new-york/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" size="sm">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<FontBoldIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<FontItalicIcon class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<UnderlineIcon class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -1 +0,0 @@
export { default as AccordionDemo } from './AccordionDemo.vue'

View File

@ -2,9 +2,10 @@
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import type { Calendar } from 'v-calendar' import type { Calendar } from 'v-calendar'
import { DatePicker } from 'v-calendar' import { DatePicker } from 'v-calendar'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next' import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'
import { computed, nextTick, onMounted, ref } from 'vue' import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
import { buttonVariants } from '@/lib/registry/default/ui/button' import { isVCalendarSlot } from '.'
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
/* Extracted from v-calendar */ /* Extracted from v-calendar */
@ -63,16 +64,26 @@ onMounted(async () => {
if (modelValue.value instanceof Date && calendarRef.value) if (modelValue.value instanceof Date && calendarRef.value)
calendarRef.value.focusDate(modelValue.value) calendarRef.value.focusDate(modelValue.value)
}) })
const $slots = useSlots()
const vCalendarSlots = computed(() => {
return Object.keys($slots)
.filter(name => isVCalendarSlot(name))
.reduce((obj: Record<string, any>, key: string) => {
obj[key] = $slots[key]
return obj
}, {})
})
</script> </script>
<template> <template>
<div class="relative"> <div class="relative">
<div class="absolute flex justify-between w-full px-4 top-3 z-[1]"> <div v-if="$attrs.mode !== 'time'" class="absolute flex justify-between w-full px-4 top-3 z-[1]">
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('prev')"> <button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('prev')">
<ChevronLeft class="w-4 h-4" /> <ChevronLeftIcon class="w-4 h-4" />
</button> </button>
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('next')"> <button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('next')">
<ChevronRight class="w-4 h-4" /> <ChevronRightIcon class="w-4 h-4" />
</button> </button>
</div> </div>
@ -85,7 +96,11 @@ onMounted(async () => {
trim-weeks trim-weeks
:transition="'none'" :transition="'none'"
:columns="columns" :columns="columns"
/> >
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</DatePicker>
</div> </div>
</template> </template>
@ -242,4 +257,49 @@ onMounted(async () => {
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate))); -webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
transform: translateY(calc(-1 * var(--vc-slide-translate))); transform: translateY(calc(-1 * var(--vc-slide-translate)));
} }
/**
* Timepicker styles
*/
.vc-time-picker {
@apply flex flex-col items-center p-2;
}
.vc-time-picker.vc-invalid {
@apply pointer-events-none opacity-50;
}
.vc-time-picker.vc-attached {
@apply border-t border-solid border-secondary mt-2;
}
.vc-time-picker > * + * {
@apply mt-1;
}
.vc-time-header {
@apply flex items-center text-sm font-semibold uppercase mt-1 px-1 leading-6;
}
.vc-time-select-group {
@apply inline-flex items-center px-1 rounded-md bg-primary-foreground border border-solid border-secondary;
}
.vc-time-select-group .vc-base-icon {
@apply mr-1 text-primary stroke-primary;
}
.vc-time-select-group select {
@apply bg-primary-foreground p-1 appearance-none outline-none text-center;
}
.vc-time-weekday {
@apply text-muted-foreground tracking-wide;
}
.vc-time-month {
@apply text-primary ml-2;
}
.vc-time-day {
@apply text-primary ml-1;
}
.vc-time-year {
@apply text-muted-foreground ml-2;
}
.vc-time-colon {
@apply mb-0.5;
}
.vc-time-decimal {
@apply ml-0.5;
}
</style> </style>

View File

@ -1 +1,22 @@
export { default as Calendar } from './Calendar.vue' export { default as Calendar } from './Calendar.vue'
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
const validSlots: CalendarSlotName[] = [
'day-content',
'day-popover',
'dp-footer',
'footer',
'header-title-wrapper',
'header-title',
'header-prev-button',
'header-next-button',
'nav',
'nav-prev-button',
'nav-next-button',
'page',
'time-header',
]
return validSlots.includes(slotName as CalendarSlotName)
}

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ArrowRight } from 'lucide-vue-next' import { ArrowRightIcon } from '@radix-icons/vue'
import { useCarousel } from './useCarousel' import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface' import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -14,7 +14,7 @@ const { orientation, canScrollNext, scrollNext } = useCarousel()
<Button <Button
:disabled="!canScrollNext" :disabled="!canScrollNext"
:class="cn( :class="cn(
'absolute h-10 w-10 rounded-full p-0', 'touch-manipulation absolute h-8 w-8 rounded-full p-0',
orientation === 'horizontal' orientation === 'horizontal'
? '-right-12 top-1/2 -translate-y-1/2' ? '-right-12 top-1/2 -translate-y-1/2'
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90', : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
@ -24,7 +24,7 @@ const { orientation, canScrollNext, scrollNext } = useCarousel()
@click="scrollNext" @click="scrollNext"
> >
<slot> <slot>
<ArrowRight class="h-4 w-4 text-current" /> <ArrowRightIcon class="h-4 w-4 text-current" />
</slot> </slot>
</Button> </Button>
</template> </template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ArrowLeft } from 'lucide-vue-next' import { ArrowLeftIcon } from '@radix-icons/vue'
import { useCarousel } from './useCarousel' import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface' import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -14,7 +14,7 @@ const { orientation, canScrollPrev, scrollPrev } = useCarousel()
<Button <Button
:disabled="!canScrollPrev" :disabled="!canScrollPrev"
:class="cn( :class="cn(
'absolute h-10 w-10 rounded-full p-0', 'touch-manipulation absolute h-8 w-8 rounded-full p-0',
orientation === 'horizontal' orientation === 'horizontal'
? '-left-12 top-1/2 -translate-y-1/2' ? '-left-12 top-1/2 -translate-y-1/2'
: '-top-12 left-1/2 -translate-x-1/2 rotate-90', : '-top-12 left-1/2 -translate-x-1/2 rotate-90',
@ -24,7 +24,7 @@ const { orientation, canScrollPrev, scrollPrev } = useCarousel()
@click="scrollPrev" @click="scrollPrev"
> >
<slot> <slot>
<ArrowLeft class="h-4 w-4 text-current" /> <ArrowLeftIcon class="h-4 w-4 text-current" />
</slot> </slot>
</Button> </Button>
</template> </template>

View File

@ -19,6 +19,7 @@ const props = withDefaults(
defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(), { defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(), {
position: 'popper', position: 'popper',
sideOffset: 4, sideOffset: 4,
avoidCollisions: true,
}, },
) )
const emits = defineEmits<SelectContentEmits>() const emits = defineEmits<SelectContentEmits>()

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { type HTMLAttributes, computed, provide } from 'vue'
import { ToggleGroupRoot, type ToggleGroupRootEmits, type ToggleGroupRootProps, useForwardPropsEmits } from 'radix-vue'
import type { toggleVariants } from '@/lib/registry/new-york/ui/toggle'
import { cn } from '@/lib/utils'
type ToggleGroupVariants = VariantProps<typeof toggleVariants>
const props = defineProps<ToggleGroupRootProps & {
class?: HTMLAttributes['class']
variant?: ToggleGroupVariants['variant']
size?: ToggleGroupVariants['size']
}>()
const emits = defineEmits<ToggleGroupRootEmits>()
provide('toggleGroup', {
variant: props.variant,
size: props.size,
})
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps.value, emits)
</script>
<template>
<ToggleGroupRoot v-bind="forwarded" :class="cn('flex items-center justify-center gap-1', props.class)">
<slot />
</ToggleGroupRoot>
</template>

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { type HTMLAttributes, computed, inject } from 'vue'
import { ToggleGroupItem, type ToggleGroupItemProps, useForwardProps } from 'radix-vue'
import { toggleVariants } from '@/lib/registry/new-york/ui/toggle'
import { cn } from '@/lib/utils'
type ToggleGroupVariants = VariantProps<typeof toggleVariants>
const props = defineProps<ToggleGroupItemProps & {
class?: HTMLAttributes['class']
variant?: ToggleGroupVariants['variant']
size?: ToggleGroupVariants['size']
}>()
const context = inject<ToggleGroupVariants>('toggleGroup')
const delegatedProps = computed(() => {
const { class: _, variant, size, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps.value)
</script>
<template>
<ToggleGroupItem
v-bind="forwardedProps" :class="cn(toggleVariants({
variant: context?.variant || variant,
size: context?.size || size,
}), props.class)"
>
<slot />
</ToggleGroupItem>
</template>

View File

@ -0,0 +1,2 @@
export { default as ToggleGroup } from './ToggleGroup.vue'
export { default as ToggleGroupItem } from './ToggleGroupItem.vue'

View File

@ -610,6 +610,20 @@
], ],
"type": "components:ui" "type": "components:ui"
}, },
{
"name": "toggle-group",
"dependencies": [],
"registryDependencies": [
"toggle",
"utils"
],
"files": [
"ui/toggle-group/ToggleGroup.vue",
"ui/toggle-group/ToggleGroupItem.vue",
"ui/toggle-group/index.ts"
],
"type": "components:ui"
},
{ {
"name": "tooltip", "name": "tooltip",
"dependencies": [], "dependencies": [],

View File

@ -7,11 +7,11 @@
"files": [ "files": [
{ {
"name": "Button.vue", "name": "Button.vue",
"content": "<script setup lang=\"ts\">\nimport { Primitive, type PrimitiveProps } from 'radix-vue'\nimport { buttonVariants } from '.'\nimport { cn } from '@/lib/utils'\n\ninterface Props extends PrimitiveProps {\n variant?: NonNullable<Parameters<typeof buttonVariants>[0]>['variant']\n size?: NonNullable<Parameters<typeof buttonVariants>[0]>['size']\n as?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n as: 'button',\n})\n</script>\n\n<template>\n <Primitive\n :as=\"as\"\n :as-child=\"asChild\"\n :class=\"cn(buttonVariants({ variant, size }), $attrs.class ?? '')\"\n >\n <slot />\n </Primitive>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority'\nimport { Primitive, type PrimitiveProps } from 'radix-vue'\nimport { buttonVariants } from '.'\nimport { cn } from '@/lib/utils'\n\ninterface ButtonVariantProps extends VariantProps<typeof buttonVariants> {}\n\ninterface Props extends PrimitiveProps {\n variant?: ButtonVariantProps['variant']\n size?: ButtonVariantProps['size']\n as?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n variant: 'default',\n size: 'default',\n as: 'button',\n})\n</script>\n\n<template>\n <Primitive\n :as=\"as\"\n :as-child=\"asChild\"\n :class=\"cn(buttonVariants({ variant, size }), $attrs.class ?? '')\"\n >\n <slot />\n </Primitive>\n</template>\n"
}, },
{ {
"name": "index.ts", "name": "index.ts",
"content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-10 px-4 py-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n" "content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-10 px-4 py-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n"
} }
], ],
"type": "components:ui" "type": "components:ui"

File diff suppressed because one or more lines are too long

View File

@ -23,11 +23,11 @@
}, },
{ {
"name": "CarouselNext.vue", "name": "CarouselNext.vue",
"content": "<script setup lang=\"ts\">\nimport { ChevronRight } from 'lucide-vue-next'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/default/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollNext, scrollNext } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollNext\"\n :class=\"cn(\n 'absolute h-10 w-10 rounded-full p-0',\n orientation === 'horizontal'\n ? '-right-12 top-1/2 -translate-y-1/2'\n : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollNext\"\n >\n <slot>\n <ChevronRight class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport { ArrowRight } from 'lucide-vue-next'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/default/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollNext, scrollNext } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollNext\"\n :class=\"cn(\n 'touch-manipulation absolute h-8 w-8 rounded-full p-0',\n orientation === 'horizontal'\n ? '-right-12 top-1/2 -translate-y-1/2'\n : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollNext\"\n >\n <slot>\n <ArrowRight class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n"
}, },
{ {
"name": "CarouselPrevious.vue", "name": "CarouselPrevious.vue",
"content": "<script setup lang=\"ts\">\nimport { ChevronLeft } from 'lucide-vue-next'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/default/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollPrev, scrollPrev } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollPrev\"\n :class=\"cn(\n 'absolute h-10 w-10 rounded-full p-0',\n orientation === 'horizontal'\n ? '-left-12 top-1/2 -translate-y-1/2'\n : '-top-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollPrev\"\n >\n <slot>\n <ChevronLeft class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport { ArrowLeft } from 'lucide-vue-next'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/default/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollPrev, scrollPrev } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollPrev\"\n :class=\"cn(\n 'touch-manipulation absolute h-8 w-8 rounded-full p-0',\n orientation === 'horizontal'\n ? '-left-12 top-1/2 -translate-y-1/2'\n : '-top-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollPrev\"\n >\n <slot>\n <ArrowLeft class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n"
}, },
{ {
"name": "index.ts", "name": "index.ts",

View File

@ -11,7 +11,7 @@
}, },
{ {
"name": "RadioGroupItem.vue", "name": "RadioGroupItem.vue",
"content": "<script setup lang=\"ts\">\nimport {\n RadioGroupIndicator,\n RadioGroupItem,\n type RadioGroupItemProps,\n} from 'radix-vue'\nimport { Circle } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<RadioGroupItemProps & { class?: string }>()\n</script>\n\n<template>\n <RadioGroupItem\n v-bind=\"props\"\n :class=\"\n cn(\n 'aspect-square h-4 w-4 rounded-full cursor-pointer flex justify-center items-center border border-primary disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n )\n \"\n >\n <RadioGroupIndicator\n :class=\"cn('flex items-center justify-center', props.class)\"\n >\n <Circle class=\"w-2.5 h-2.5 text-foreground\" />\n </RadioGroupIndicator>\n </RadioGroupItem>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport {\n RadioGroupIndicator,\n RadioGroupItem,\n type RadioGroupItemProps,\n} from 'radix-vue'\nimport { Circle } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<RadioGroupItemProps & { class?: string }>()\n</script>\n\n<template>\n <RadioGroupItem\n v-bind=\"props\"\n :class=\"\n cn(\n 'aspect-square h-4 w-4 rounded-full cursor-pointer flex justify-center items-center border border-primary disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n )\n \"\n >\n <RadioGroupIndicator\n :class=\"cn('flex items-center justify-center', props.class)\"\n >\n <Circle class=\"w-2.5 h-2.5 text-current fill-current\" />\n </RadioGroupIndicator>\n </RadioGroupItem>\n</template>\n"
}, },
{ {
"name": "index.ts", "name": "index.ts",

View File

@ -11,7 +11,7 @@
}, },
{ {
"name": "SelectContent.vue", "name": "SelectContent.vue",
"content": "<script setup lang=\"ts\">\nimport {\n SelectContent,\n type SelectContentEmits,\n type SelectContentProps,\n SelectPortal,\n SelectViewport,\n useForwardPropsEmits,\n} from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectContentProps & { class?: string }>(), {\n position: 'popper',\n sideOffset: 4,\n },\n)\nconst emits = defineEmits<SelectContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <SelectPortal>\n <SelectContent\n v-bind=\"{ ...forwarded, ...$attrs }\"\n :class=\"\n cn(\n 'relative z-50 min-w-[10rem] overflow-hidden rounded-md bg-background border border-border text-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n position === 'popper'\n && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n props.class,\n )\n \"\n >\n <SelectViewport\n :class=\"\n cn('p-1',\n position === 'popper'\n && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]')\"\n >\n <slot />\n </SelectViewport>\n </SelectContent>\n </SelectPortal>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport {\n SelectContent,\n type SelectContentEmits,\n type SelectContentProps,\n SelectPortal,\n SelectViewport,\n useForwardPropsEmits,\n} from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectContentProps & { class?: string }>(), {\n position: 'popper',\n sideOffset: 4,\n avoidCollisions: true,\n },\n)\nconst emits = defineEmits<SelectContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <SelectPortal>\n <SelectContent\n v-bind=\"{ ...forwarded, ...$attrs }\"\n :class=\"\n cn(\n 'relative z-50 min-w-[10rem] overflow-hidden rounded-md bg-background border border-border text-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-[--radix-popper-available-height]',\n position === 'popper'\n && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n props.class,\n )\n \"\n >\n <SelectViewport\n :class=\"\n cn('p-1',\n position === 'popper'\n && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]')\"\n >\n <slot />\n </SelectViewport>\n </SelectContent>\n </SelectPortal>\n</template>\n"
}, },
{ {
"name": "SelectGroup.vue", "name": "SelectGroup.vue",
@ -35,7 +35,7 @@
}, },
{ {
"name": "SelectTrigger.vue", "name": "SelectTrigger.vue",
"content": "<script setup lang=\"ts\">\nimport { SelectIcon, SelectTrigger, type SelectTriggerProps } from 'radix-vue'\nimport { ChevronDown } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectTriggerProps & { class?: string; invalid?: boolean }>(),\n {\n class: '',\n invalid: false,\n },\n)\n</script>\n\n<template>\n <SelectTrigger\n v-bind=\"props\"\n :class=\"[\n cn(\n 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n ),\n props.invalid\n ? '!ring-destructive ring-2 placeholder:!text-destructive'\n : '',\n ]\"\n >\n <slot />\n <SelectIcon as-child>\n <ChevronDown class=\"w-4 h-4 opacity-50\" />\n </SelectIcon>\n </SelectTrigger>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport { SelectIcon, SelectTrigger, type SelectTriggerProps } from 'radix-vue'\nimport { ChevronDown } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectTriggerProps & { class?: string; invalid?: boolean }>(),\n {\n class: '',\n invalid: false,\n },\n)\n</script>\n\n<template>\n <SelectTrigger\n v-bind=\"props\"\n :class=\"[\n cn(\n 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 whitespace-nowrap [&>span]:truncate [&>span]:min-w-0',\n props.class,\n ),\n props.invalid\n ? '!ring-destructive ring-2 placeholder:!text-destructive'\n : '',\n ]\"\n >\n <slot />\n <SelectIcon as-child>\n <ChevronDown class=\"w-4 h-4 opacity-50\" />\n </SelectIcon>\n </SelectTrigger>\n</template>\n"
}, },
{ {
"name": "SelectValue.vue", "name": "SelectValue.vue",

View File

@ -0,0 +1,23 @@
{
"name": "toggle-group",
"dependencies": [],
"registryDependencies": [
"toggle",
"utils"
],
"files": [
{
"name": "ToggleGroup.vue",
"content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority'\nimport { type HTMLAttributes, computed, provide } from 'vue'\nimport { ToggleGroupRoot, type ToggleGroupRootEmits, type ToggleGroupRootProps, useForwardPropsEmits } from 'radix-vue'\nimport type { toggleVariants } from '@/lib/registry/default/ui/toggle'\nimport { cn } from '@/lib/utils'\n\ntype ToggleGroupVariants = VariantProps<typeof toggleVariants>\n\nconst props = defineProps<ToggleGroupRootProps & {\n class?: HTMLAttributes['class']\n variant?: ToggleGroupVariants['variant']\n size?: ToggleGroupVariants['size']\n}>()\nconst emits = defineEmits<ToggleGroupRootEmits>()\n\nprovide('toggleGroup', {\n variant: props.variant,\n size: props.size,\n})\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps.value, emits)\n</script>\n\n<template>\n <ToggleGroupRoot v-bind=\"forwarded\" :class=\"cn('flex items-center justify-center gap-1', props.class)\">\n <slot />\n </ToggleGroupRoot>\n</template>\n"
},
{
"name": "ToggleGroupItem.vue",
"content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority'\nimport { type HTMLAttributes, computed, inject } from 'vue'\nimport { ToggleGroupItem, type ToggleGroupItemProps, useForwardProps } from 'radix-vue'\nimport { toggleVariants } from '@/lib/registry/default/ui/toggle'\nimport { cn } from '@/lib/utils'\n\ntype ToggleGroupVariants = VariantProps<typeof toggleVariants>\n\nconst props = defineProps<ToggleGroupItemProps & {\n class?: HTMLAttributes['class']\n variant?: ToggleGroupVariants['variant']\n size?: ToggleGroupVariants['size']\n}>()\n\nconst context = inject<ToggleGroupVariants>('toggleGroup')\n\nconst delegatedProps = computed(() => {\n const { class: _, variant, size, ...delegated } = props\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps.value)\n</script>\n\n<template>\n <ToggleGroupItem\n v-bind=\"forwardedProps\" :class=\"cn(toggleVariants({\n variant: context?.variant || variant,\n size: context?.size || size,\n }), props.class)\"\n >\n <slot />\n </ToggleGroupItem>\n</template>\n"
},
{
"name": "index.ts",
"content": "export { default as ToggleGroup } from './ToggleGroup.vue'\nexport { default as ToggleGroupItem } from './ToggleGroupItem.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -7,11 +7,11 @@
"files": [ "files": [
{ {
"name": "Button.vue", "name": "Button.vue",
"content": "<script setup lang=\"ts\">\nimport { Primitive, type PrimitiveProps } from 'radix-vue'\nimport { buttonVariants } from '.'\nimport { cn } from '@/lib/utils'\n\ninterface Props extends PrimitiveProps {\n variant?: NonNullable<Parameters<typeof buttonVariants>[0]>['variant']\n size?: NonNullable<Parameters<typeof buttonVariants>[0]>['size']\n as?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n as: 'button',\n})\n</script>\n\n<template>\n <Primitive\n :as=\"as\"\n :as-child=\"asChild\"\n :class=\"cn(buttonVariants({ variant, size }), $attrs.class ?? '')\"\n >\n <slot />\n </Primitive>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority'\nimport { Primitive, type PrimitiveProps } from 'radix-vue'\nimport { buttonVariants } from '.'\nimport { cn } from '@/lib/utils'\n\ninterface ButtonVariantProps extends VariantProps<typeof buttonVariants> {}\n\ninterface Props extends PrimitiveProps {\n variant?: ButtonVariantProps['variant']\n size?: ButtonVariantProps['size']\n as?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n variant: 'default',\n size: 'default',\n as: 'button',\n})\n</script>\n\n<template>\n <Primitive\n :as=\"as\"\n :as-child=\"asChild\"\n :class=\"cn(buttonVariants({ variant, size }), $attrs.class ?? '')\"\n >\n <slot />\n </Primitive>\n</template>\n"
}, },
{ {
"name": "index.ts", "name": "index.ts",
"content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'bg-primary text-primary-foreground shadow hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',\n outline:\n 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-9 px-4 py-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-8',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n" "content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'bg-primary text-primary-foreground shadow hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',\n outline:\n 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-9 px-4 py-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-8',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n"
} }
], ],
"type": "components:ui" "type": "components:ui"

File diff suppressed because one or more lines are too long

View File

@ -23,11 +23,11 @@
}, },
{ {
"name": "CarouselNext.vue", "name": "CarouselNext.vue",
"content": "<script setup lang=\"ts\">\nimport { ChevronRightIcon } from '@radix-icons/vue'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/new-york/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollNext, scrollNext } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollNext\"\n :class=\"cn(\n 'absolute h-10 w-10 rounded-full p-0',\n orientation === 'horizontal'\n ? '-right-12 top-1/2 -translate-y-1/2'\n : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollNext\"\n >\n <slot>\n <ChevronRightIcon class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport { ArrowRightIcon } from '@radix-icons/vue'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/new-york/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollNext, scrollNext } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollNext\"\n :class=\"cn(\n 'touch-manipulation absolute h-8 w-8 rounded-full p-0',\n orientation === 'horizontal'\n ? '-right-12 top-1/2 -translate-y-1/2'\n : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollNext\"\n >\n <slot>\n <ArrowRightIcon class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n"
}, },
{ {
"name": "CarouselPrevious.vue", "name": "CarouselPrevious.vue",
"content": "<script setup lang=\"ts\">\nimport { ChevronLeftIcon } from '@radix-icons/vue'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/new-york/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollPrev, scrollPrev } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollPrev\"\n :class=\"cn(\n 'absolute h-10 w-10 rounded-full p-0',\n orientation === 'horizontal'\n ? '-left-12 top-1/2 -translate-y-1/2'\n : '-top-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollPrev\"\n >\n <slot>\n <ChevronLeftIcon class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport { ArrowLeftIcon } from '@radix-icons/vue'\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/lib/registry/new-york/ui/button'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation, canScrollPrev, scrollPrev } = useCarousel()\n</script>\n\n<template>\n <Button\n :disabled=\"!canScrollPrev\"\n :class=\"cn(\n 'touch-manipulation absolute h-8 w-8 rounded-full p-0',\n orientation === 'horizontal'\n ? '-left-12 top-1/2 -translate-y-1/2'\n : '-top-12 left-1/2 -translate-x-1/2 rotate-90',\n props.class,\n )\"\n variant=\"outline\"\n @click=\"scrollPrev\"\n >\n <slot>\n <ArrowLeftIcon class=\"h-4 w-4 text-current\" />\n </slot>\n </Button>\n</template>\n"
}, },
{ {
"name": "index.ts", "name": "index.ts",

View File

@ -11,7 +11,7 @@
}, },
{ {
"name": "SelectContent.vue", "name": "SelectContent.vue",
"content": "<script setup lang=\"ts\">\nimport {\n SelectContent,\n type SelectContentEmits,\n type SelectContentProps,\n SelectPortal,\n SelectViewport,\n useForwardPropsEmits,\n} from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectContentProps & { class?: string }>(), {\n position: 'popper',\n sideOffset: 4,\n },\n)\nconst emits = defineEmits<SelectContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <SelectPortal>\n <SelectContent\n v-bind=\"{ ...forwarded, ...$attrs }\"\n :class=\"\n cn(\n 'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n position === 'popper'\n && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n props.class,\n )\n \"\n >\n <SelectViewport\n :class=\"\n cn('p-0',\n position === 'popper'\n && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]')\"\n >\n <slot />\n </SelectViewport>\n </SelectContent>\n </SelectPortal>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport {\n SelectContent,\n type SelectContentEmits,\n type SelectContentProps,\n SelectPortal,\n SelectViewport,\n useForwardPropsEmits,\n} from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectContentProps & { class?: string }>(), {\n position: 'popper',\n sideOffset: 4,\n avoidCollisions: true,\n },\n)\nconst emits = defineEmits<SelectContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <SelectPortal>\n <SelectContent\n v-bind=\"{ ...forwarded, ...$attrs }\"\n :class=\"\n cn(\n 'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-[--radix-popper-available-height]',\n position === 'popper'\n && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n props.class,\n )\n \"\n >\n <SelectViewport\n :class=\"\n cn('p-0',\n position === 'popper'\n && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]')\"\n >\n <slot />\n </SelectViewport>\n </SelectContent>\n </SelectPortal>\n</template>\n"
}, },
{ {
"name": "SelectGroup.vue", "name": "SelectGroup.vue",
@ -35,7 +35,7 @@
}, },
{ {
"name": "SelectTrigger.vue", "name": "SelectTrigger.vue",
"content": "<script setup lang=\"ts\">\nimport { SelectIcon, SelectTrigger, type SelectTriggerProps } from 'radix-vue'\nimport { ChevronDownIcon } from '@radix-icons/vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectTriggerProps & { class?: string; invalid?: boolean }>(),\n {\n class: '',\n invalid: false,\n },\n)\n</script>\n\n<template>\n <SelectTrigger\n v-bind=\"props\"\n :class=\"[\n cn(\n 'flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n ),\n props.invalid\n ? '!ring-destructive ring-2 placeholder:!text-destructive'\n : '',\n ]\"\n >\n <slot />\n <SelectIcon as-child>\n <ChevronDownIcon class=\"w-4 h-4 opacity-50\" />\n </SelectIcon>\n </SelectTrigger>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport { SelectIcon, SelectTrigger, type SelectTriggerProps } from 'radix-vue'\nimport { ChevronDownIcon } from '@radix-icons/vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n defineProps<SelectTriggerProps & { class?: string; invalid?: boolean }>(),\n {\n class: '',\n invalid: false,\n },\n)\n</script>\n\n<template>\n <SelectTrigger\n v-bind=\"props\"\n :class=\"[\n cn(\n 'flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 whitespace-nowrap [&>span]:truncate [&>span]:min-w-0',\n props.class,\n ),\n props.invalid\n ? '!ring-destructive ring-2 placeholder:!text-destructive'\n : '',\n ]\"\n >\n <slot />\n <SelectIcon as-child>\n <ChevronDownIcon class=\"w-4 h-4 opacity-50\" />\n </SelectIcon>\n </SelectTrigger>\n</template>\n"
}, },
{ {
"name": "SelectValue.vue", "name": "SelectValue.vue",

View File

@ -0,0 +1,23 @@
{
"name": "toggle-group",
"dependencies": [],
"registryDependencies": [
"toggle",
"utils"
],
"files": [
{
"name": "ToggleGroup.vue",
"content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority'\nimport { type HTMLAttributes, computed, provide } from 'vue'\nimport { ToggleGroupRoot, type ToggleGroupRootEmits, type ToggleGroupRootProps, useForwardPropsEmits } from 'radix-vue'\nimport type { toggleVariants } from '@/lib/registry/new-york/ui/toggle'\nimport { cn } from '@/lib/utils'\n\ntype ToggleGroupVariants = VariantProps<typeof toggleVariants>\n\nconst props = defineProps<ToggleGroupRootProps & {\n class?: HTMLAttributes['class']\n variant?: ToggleGroupVariants['variant']\n size?: ToggleGroupVariants['size']\n}>()\nconst emits = defineEmits<ToggleGroupRootEmits>()\n\nprovide('toggleGroup', {\n variant: props.variant,\n size: props.size,\n})\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps.value, emits)\n</script>\n\n<template>\n <ToggleGroupRoot v-bind=\"forwarded\" :class=\"cn('flex items-center justify-center gap-1', props.class)\">\n <slot />\n </ToggleGroupRoot>\n</template>\n"
},
{
"name": "ToggleGroupItem.vue",
"content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority'\nimport { type HTMLAttributes, computed, inject } from 'vue'\nimport { ToggleGroupItem, type ToggleGroupItemProps, useForwardProps } from 'radix-vue'\nimport { toggleVariants } from '@/lib/registry/new-york/ui/toggle'\nimport { cn } from '@/lib/utils'\n\ntype ToggleGroupVariants = VariantProps<typeof toggleVariants>\n\nconst props = defineProps<ToggleGroupItemProps & {\n class?: HTMLAttributes['class']\n variant?: ToggleGroupVariants['variant']\n size?: ToggleGroupVariants['size']\n}>()\n\nconst context = inject<ToggleGroupVariants>('toggleGroup')\n\nconst delegatedProps = computed(() => {\n const { class: _, variant, size, ...delegated } = props\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps.value)\n</script>\n\n<template>\n <ToggleGroupItem\n v-bind=\"forwardedProps\" :class=\"cn(toggleVariants({\n variant: context?.variant || variant,\n size: context?.size || size,\n }), props.class)\"\n >\n <slot />\n </ToggleGroupItem>\n</template>\n"
},
{
"name": "index.ts",
"content": "export { default as ToggleGroup } from './ToggleGroup.vue'\nexport { default as ToggleGroupItem } from './ToggleGroupItem.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -6,6 +6,10 @@
"type": "string", "type": "string",
"enum": ["default", "new-york"] "enum": ["default", "new-york"]
}, },
"typescript": {
"type": "boolean",
"default": true
},
"tailwind": { "tailwind": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -19,11 +23,17 @@
"type": "string" "type": "string"
}, },
"cssVariables": { "cssVariables": {
"type": "boolean" "type": "boolean",
"default": true
} }
}, },
"required": ["config", "css", "baseColor", "cssVariables"] "required": ["config", "css", "baseColor", "cssVariables"]
}, },
"framework": {
"type": "string",
"enum": ["nuxt", "vite", "laravel", "astro"],
"default": "vite"
},
"aliases": { "aliases": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -12,6 +12,6 @@
"sourceMap": true, "sourceMap": true,
"outDir": "dist" "outDir": "dist"
}, },
"include": ["src", ".vitepress/**/*.vue", ".vitepress/**/*.mts", ".vitepress/**/*.vue", "src/lib/**/*"], "include": ["src", ".vitepress/**/*.vue", "scripts/build-registry.ts", ".vitepress/**/*.mts", ".vitepress/**/*.vue", "src/lib/**/*"],
"exclude": ["node_modules", "./scripts/build-registry.ts"] "exclude": ["node_modules"]
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "shadcn-vue", "name": "shadcn-vue",
"version": "0.8.6", "version": "0.8.7",
"private": true, "private": true,
"packageManager": "pnpm@8.10.2", "packageManager": "pnpm@8.10.2",
"license": "MIT", "license": "MIT",

View File

@ -1,7 +1,7 @@
{ {
"name": "shadcn-vue", "name": "shadcn-vue",
"type": "module", "type": "module",
"version": "0.8.6", "version": "0.8.7",
"description": "Add components to your apps.", "description": "Add components to your apps.",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@ -63,7 +63,7 @@
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"ora": "^7.0.1", "ora": "^7.0.1",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"radix-vue": "^1.2.3", "radix-vue": "^1.3.0",
"recast": "^0.23.4", "recast": "^0.23.4",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"ts-morph": "^19.0.0", "ts-morph": "^19.0.0",

View File

@ -178,7 +178,7 @@ export async function promptForConfig(
]) ])
const config = rawConfigSchema.parse({ const config = rawConfigSchema.parse({
// $schema: 'https://ui.shadcn.com/schema.json', $schema: 'https://shadcn-vue.com/schema.json',
style: options.style, style: options.style,
typescript: options.typescript, typescript: options.typescript,
framework: options.framework, framework: options.framework,

View File

@ -51,6 +51,7 @@ export const TAILWIND_CONFIG_WITH_VARIABLES = `const animate = require("tailwind
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: ["class"], darkMode: ["class"],
safelist: ["dark"],
<% if (framework === 'vite') { %> <% if (framework === 'vite') { %>
content: [ content: [
'./pages/**/*.{<%- extension %>,<%- extension %>x,vue}', './pages/**/*.{<%- extension %>,<%- extension %>x,vue}',

View File

@ -13,7 +13,7 @@
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"lucide-vue-next": "^0.276.0", "lucide-vue-next": "^0.276.0",
"radix-vue": "^1.2.3", "radix-vue": "^1.3.0",
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },

View File

@ -47,6 +47,7 @@ exports[`handle tailwind config template correctly 2`] = `
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
darkMode: [\\"class\\"], darkMode: [\\"class\\"],
safelist: [\\"dark\\"],
<% if (framework === 'vite') { %> <% if (framework === 'vite') { %>
content: [ content: [
'./pages/**/*.{<%- extension %>,<%- extension %>x,vue}', './pages/**/*.{<%- extension %>,<%- extension %>x,vue}',

View File

@ -1,7 +1,7 @@
{ {
"name": "shadcn-nuxt", "name": "shadcn-nuxt",
"type": "module", "type": "module",
"version": "0.8.6", "version": "0.8.7",
"description": "Add shadcn-vue module to Nuxt", "description": "Add shadcn-vue module to Nuxt",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@ -36,8 +36,7 @@
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "^3.8.2", "@nuxt/kit": "^3.8.2",
"recast": "^0.23.4", "oxc-parser": "^0.2.0"
"ts-morph": "^19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/devtools": "latest", "@nuxt/devtools": "latest",

View File

@ -0,0 +1,45 @@
<script setup lang="ts">
import emblaCarouselVue from 'embla-carousel-vue'
import { useProvideCarousel } from './useCarousel'
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
orientation: 'horizontal',
})
const emits = defineEmits<CarouselEmits>()
const carouselArgs = useProvideCarousel(props, emits)
defineExpose(carouselArgs)
function onKeyDown(event: KeyboardEvent) {
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'
if (event.key === prevKey) {
event.preventDefault()
carouselArgs.scrollPrev()
return
}
if (event.key === nextKey) {
event.preventDefault()
carouselArgs.scrollNext()
}
}
</script>
<template>
<div
:class="cn('relative', props.class)"
role="region"
aria-roledescription="carousel"
tabindex="0"
@keydown="onKeyDown"
>
<slot v-bind="carouselArgs" />
</div>
</template>

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import type { WithClassAsProps } from './interface'
import { useCarousel } from './useCarousel'
import { cn } from '@/lib/utils'
defineOptions({
inheritAttrs: false,
})
const props = defineProps<WithClassAsProps>()
const { carouselRef, orientation } = useCarousel()
</script>
<template>
<div ref="carouselRef" class="overflow-hidden">
<div
:class="
cn(
'flex',
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
props.class,
)"
v-bind="$attrs"
>
<slot />
</div>
</div>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import type { WithClassAsProps } from './interface'
import { useCarousel } from './useCarousel'
import { cn } from '@/lib/utils'
const props = defineProps<WithClassAsProps>()
const { orientation } = useCarousel()
</script>
<template>
<div
role="group"
aria-roledescription="slide"
:class="cn(
'min-w-0 shrink-0 grow-0 basis-full',
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
props.class,
)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import { ChevronRight } from 'lucide-vue-next'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
const props = defineProps<WithClassAsProps>()
const { orientation, canScrollNext, scrollNext } = useCarousel()
</script>
<template>
<Button
:disabled="!canScrollNext"
:class="cn(
'absolute h-10 w-10 rounded-full p-0',
orientation === 'horizontal'
? '-right-12 top-1/2 -translate-y-1/2'
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
props.class,
)"
variant="outline"
@click="scrollNext"
>
<slot>
<ChevronRight class="h-4 w-4 text-current" />
</slot>
</Button>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import { ChevronLeft } from 'lucide-vue-next'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
const props = defineProps<WithClassAsProps>()
const { orientation, canScrollPrev, scrollPrev } = useCarousel()
</script>
<template>
<Button
:disabled="!canScrollPrev"
:class="cn(
'absolute h-10 w-10 rounded-full p-0',
orientation === 'horizontal'
? '-left-12 top-1/2 -translate-y-1/2'
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
props.class,
)"
variant="outline"
@click="scrollPrev"
>
<slot>
<ChevronLeft class="h-4 w-4 text-current" />
</slot>
</Button>
</template>

View File

@ -0,0 +1,10 @@
export { default as Carousel } from './Carousel.vue'
export { default as CarouselContent } from './CarouselContent.vue'
export { default as CarouselItem } from './CarouselItem.vue'
export { default as CarouselPrevious } from './CarouselPrevious.vue'
export { default as CarouselNext } from './CarouselNext.vue'
export { useCarousel } from './useCarousel'
export type {
EmblaCarouselType as CarouselApi,
} from 'embla-carousel'

View File

@ -0,0 +1,20 @@
import type {
EmblaCarouselType as CarouselApi,
EmblaOptionsType as CarouselOptions,
EmblaPluginType as CarouselPlugin,
} from 'embla-carousel'
import type { HTMLAttributes, Ref } from 'vue'
export interface CarouselProps {
opts?: CarouselOptions | Ref<CarouselOptions>
plugins?: CarouselPlugin[] | Ref<CarouselPlugin[]>
orientation?: 'horizontal' | 'vertical'
}
export interface CarouselEmits {
(e: 'init-api', payload: CarouselApi): void
}
export interface WithClassAsProps {
class?: HTMLAttributes['class']
}

View File

@ -0,0 +1,57 @@
import { createInjectionState } from '@vueuse/core'
import emblaCarouselVue from 'embla-carousel-vue'
import { onMounted, ref } from 'vue'
import type {
EmblaCarouselType as CarouselApi,
} from 'embla-carousel'
import type { CarouselEmits, CarouselProps } from './interface'
const [useProvideCarousel, useInjectCarousel] = createInjectionState(
({
opts, orientation, plugins,
}: CarouselProps, emits: CarouselEmits) => {
const [emblaNode, emblaApi] = emblaCarouselVue({
...opts,
axis: orientation === 'horizontal' ? 'x' : 'y',
}, plugins)
function scrollPrev() {
emblaApi.value?.scrollPrev()
}
function scrollNext() {
emblaApi.value?.scrollNext()
}
const canScrollNext = ref(true)
const canScrollPrev = ref(true)
function onSelect(api: CarouselApi) {
canScrollNext.value = api.canScrollNext()
canScrollPrev.value = api.canScrollPrev()
}
onMounted(() => {
if (!emblaApi.value)
return
emblaApi.value?.on('init', onSelect)
emblaApi.value?.on('reInit', onSelect)
emblaApi.value?.on('select', onSelect)
emits('init-api', emblaApi.value)
})
return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation }
},
)
function useCarousel() {
const carouselState = useInjectCarousel()
if (!carouselState)
throw new Error('useCarousel must be used within a <Carousel />')
return carouselState
}
export { useCarousel, useProvideCarousel }

View File

@ -11,8 +11,10 @@
"@nuxtjs/tailwindcss": "^6.10.1", "@nuxtjs/tailwindcss": "^6.10.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"embla-carousel": "8.0.0-rc19",
"embla-carousel-vue": "8.0.0-rc19",
"lucide-vue-next": "^0.276.0", "lucide-vue-next": "^0.276.0",
"radix-vue": "^1.2.3", "radix-vue": "^1.3.0",
"tailwind-merge": "^2.0.0", "tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },

View File

@ -1,8 +1,9 @@
import { readFileSync, readdirSync } from 'node:fs' import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path' import { join } from 'node:path'
import { addComponent, createResolver, defineNuxtModule } from '@nuxt/kit' import { addComponent, createResolver, defineNuxtModule } from '@nuxt/kit'
import { parse } from 'recast' import oxc from 'oxc-parser'
// TODO: add test to make sure all registry is being parse correctly
// Module options TypeScript interface definition // Module options TypeScript interface definition
export interface ModuleOptions { export interface ModuleOptions {
/** /**
@ -40,24 +41,34 @@ export default defineNuxtModule<ModuleOptions>({
try { try {
readdirSync(resolve(COMPONENT_DIR_PATH)) readdirSync(resolve(COMPONENT_DIR_PATH))
.forEach(async (dir) => { .forEach(async (dir) => {
const filePath = await resolvePath(join(COMPONENT_DIR_PATH, dir, 'index'), { extensions: ['.ts', '.js'] }) try {
const content = readFileSync(filePath, { encoding: 'utf8' }) const filePath = await resolvePath(join(COMPONENT_DIR_PATH, dir, 'index'), { extensions: ['.ts', '.js'] })
const ast = parse(content) const content = readFileSync(filePath, { encoding: 'utf8' })
const ast = oxc.parseSync(content, {
const exportedKeys: string[] = ast.program.body sourceType: 'module',
// @ts-expect-error parse return any sourceFilename: filePath,
.filter(node => node.type === 'ExportNamedDeclaration')
// @ts-expect-error parse return any
.flatMap(node => node.specifiers.map(specifier => specifier.exported.name))
.filter((key: string) => /^[A-Z]/.test(key))
exportedKeys.forEach((key) => {
addComponent({
name: `${prefix}${key}`, // name of the component to be used in vue templates
export: key, // (optional) if the component is a named (rather than default) export
filePath: resolve(filePath),
}) })
}) const program = JSON.parse(ast.program)
const exportedKeys: string[] = program.body
// @ts-expect-error parse return any
.filter(node => node.type === 'ExportNamedDeclaration')
// @ts-expect-error parse return any
.flatMap(node => node.specifiers.map(specifier => specifier.exported.name))
.filter((key: string) => /^[A-Z]/.test(key))
exportedKeys.forEach((key) => {
addComponent({
name: `${prefix}${key}`, // name of the component to be used in vue templates
export: key, // (optional) if the component is a named (rather than default) export
filePath: resolve(filePath),
})
})
}
catch (err) {
if (err instanceof Error)
console.warn('Module error: ', err.message)
}
}) })
} }
catch (err) { catch (err) {

File diff suppressed because it is too large Load Diff