Merge branch 'dev' of https://github.com/sadeghbarati/shadcn-vue into docs/add-more-info-in-vite-installation

This commit is contained in:
Sadegh Barati 2024-01-18 16:40:55 +03:30
commit 748cbb9373
111 changed files with 4389 additions and 971 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

@ -168,6 +168,12 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/card', href: '/docs/components/card',
items: [], items: [],
}, },
{
title: 'Carousel',
href: '/docs/components/carousel',
label: 'New',
items: [],
},
{ {
title: 'Checkbox', title: 'Checkbox',
href: '/docs/components/checkbox', href: '/docs/components/checkbox',
@ -324,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

@ -184,6 +184,48 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/CardWithForm.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/CardWithForm.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CardWithForm.vue'], files: ['../src/lib/registry/default/example/CardWithForm.vue'],
}, },
CarouselApi: {
name: 'CarouselApi',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselApi.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselApi.vue'],
},
CarouselDemo: {
name: 'CarouselDemo',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselDemo.vue'],
},
CarouselOrientation: {
name: 'CarouselOrientation',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselOrientation.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselOrientation.vue'],
},
CarouselPlugin: {
name: 'CarouselPlugin',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselPlugin.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselPlugin.vue'],
},
CarouselSize: {
name: 'CarouselSize',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselSize.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselSize.vue'],
},
CarouselSpacing: {
name: 'CarouselSpacing',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselSpacing.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselSpacing.vue'],
},
CheckboxDemo: { CheckboxDemo: {
name: 'CheckboxDemo', name: 'CheckboxDemo',
type: 'components:example', type: 'components:example',
@ -317,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',
@ -457,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',
@ -639,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',
@ -977,6 +1075,48 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/CardWithForm.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/CardWithForm.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CardWithForm.vue'], files: ['../src/lib/registry/new-york/example/CardWithForm.vue'],
}, },
CarouselApi: {
name: 'CarouselApi',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselApi.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselApi.vue'],
},
CarouselDemo: {
name: 'CarouselDemo',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselDemo.vue'],
},
CarouselOrientation: {
name: 'CarouselOrientation',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselOrientation.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselOrientation.vue'],
},
CarouselPlugin: {
name: 'CarouselPlugin',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselPlugin.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselPlugin.vue'],
},
CarouselSize: {
name: 'CarouselSize',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselSize.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselSize.vue'],
},
CarouselSpacing: {
name: 'CarouselSpacing',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselSpacing.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselSpacing.vue'],
},
CheckboxDemo: { CheckboxDemo: {
name: 'CheckboxDemo', name: 'CheckboxDemo',
type: 'components:example', type: 'components:example',
@ -1110,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',
@ -1250,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',
@ -1432,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"
], ],
@ -9,30 +9,34 @@
"dev": "vitepress dev", "dev": "vitepress dev",
"build": "vitepress build", "build": "vitepress build",
"preview": "vitepress preview", "preview": "vitepress preview",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc",
"typecheck:registry": "vue-tsc --noEmit -p tsconfig.registry.json", "typecheck:registry": "vue-tsc -p tsconfig.registry.json",
"build:registry": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts" "build: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-autoplay": "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"
}, },
@ -43,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.4.0", "@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.25" "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

@ -0,0 +1,282 @@
---
title: Carousel
description: A carousel with motion and swipe built using Embla.
source: apps/www/src/lib/registry/default/ui/carousel
primitive: https://www.embla-carousel.com/api
---
<ComponentPreview name="CarouselDemo" />
## About
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
## Installation
```bash
npx shadcn-vue@latest add carousel
```
## Usage
```vue
<script setup lang="ts">
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel'
</script>
<template>
<Carousel>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>
```
## Examples
### Sizes
To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`.
<ComponentPreview name="CarouselSize" />
Example
```vue title="Example" showLineNumbers {4-6}
// 33% of the carousel width.
<Carousel>
<CarouselContent>
<CarouselItem class="basis-1/3">...</CarouselItem>
<CarouselItem class="basis-1/3">...</CarouselItem>
<CarouselItem class="basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
```
Responsive
```vue title="Responsive" showLineNumbers {4-6}
// 50% on small screens and 33% on larger screens.
<Carousel>
<CarouselContent>
<CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
```
### Spacing
To set the spacing between the items, we use a `pl-[VALUE]` utility on the `<CarouselItem />` and a negative `-ml-[VALUE]` on the `<CarouselContent />`.
<Callout class="mt-6">
**Why:** I tried to use the `gap` property or a `grid` layout on the `
CarouselContent` but it required a lot of math and mental effort to get the
spacing right. I found `pl-[VALUE]` and `-ml-[VALUE]` utilities much easier to
use.
<br/><br/>
You can always adjust this in your own project if you need to.
</Callout>
<ComponentPreview name="CarouselSpacing" />
Example
```vue showLineNumbers /-ml-4/ /pl-4/
<template>
<Carousel>
<CarouselContent class="-ml-4">
<CarouselItem class="pl-4">
...
</CarouselItem>
<CarouselItem class="pl-4">
...
</CarouselItem>
<CarouselItem class="pl-4">
...
</CarouselItem>
</CarouselContent>
</Carousel>
</template>
```
Responsive
```vue showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/
<template>
<Carousel>
<CarouselContent class="-ml-2 md:-ml-4">
<CarouselItem class="pl-2 md:pl-4">
...
</CarouselItem>
<CarouselItem class="pl-2 md:pl-4">
...
</CarouselItem>
<CarouselItem class="pl-2 md:pl-4">
...
</CarouselItem>
</CarouselContent>
</Carousel>
</template>
```
### Orientation
Use the `orientation` prop to set the orientation of the carousel.
<ComponentPreview name="CarouselOrientation" />
```vue
<Carousel orientation="vertical | horizontal">
...
</Carousel>
```
## Options
You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information.
```vue showLineNumbers {3-6}
<template>
<Carousel
:opts="{
align: 'start',
loop: true,
}"
>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
</template>
```
## API
### Method 1
Use the `@init-api` emit method on `<Carousel />` component to set the instance of the API.
<ComponentPreview name="CarouselApi" />
### Method 2
You can access it through setting a template ref on the `<Carousel />` component.
```vue showLineNumbers {2,5,9}
<script setup>
const carouselContainerRef = ref<InstanceType<typeof Carousel> | null>(null)
function accessApi() {
carouselContainerRef.value?.carouselApi.on('select', () => {})
}
</script>
<template>
<Carousel ref="carouselContainerRef">
...
</Carousel>
</template>
```
## Events
You can listen to events using the API. To get the API instance use the `@init-api` emit method on the `<Carousel />` component
```vue showLineNumbers {5,7-9,25}
<script setup>
import { nextTick, ref, watch } from 'vue'
import { useCarousel } from '@/components/ui/carousel'
const api = ref<CarouselApi>()
function setApi(val: CarouselApi) {
api.value = val
}
const stop = watch(api, (api) => {
if (!api)
return
// Watch only once or use watchOnce() in @vueuse/core
nextTick(() => stop())
api.on('select', () => {
// Do something on select.
})
})
</script>
<template>
<Carousel @init-api="setApi">
...
</Carousel>
</template>
```
See the [Embla Carousel docs](https://www.embla-carousel.com/api/events/) for more information on using events.
## Slot Props
You can get the reactive slot props like `carouselRef, canScrollNext..Prev, scrollNext..Prev` using the `v-slot` directive in the `<Carousel v-slot="slotProps" />` component to extend the functionality.
```vue showLineNumbers {2}
<template>
<Carousel v-slot="{ canScrollNext, canScrollPrev }">
...
<CarouselPrevious v-if="canScrollPrev" />
<CarouselNext v-if="canScrollNext" />
</Carousel>
</template>
```
## Plugins
You can use the `plugins` prop to add plugins to the carousel.
```bash
npm i embla-carousel-autoplay
```
```vue showLineNumbers {2,8-10}
<script setup lang="ts">
import Autoplay from 'embla-carousel-autoplay'
</script>
<template>
<Carousel
class="w-full max-w-xs"
:plugins="[Autoplay({
delay: 2000,
})]"
>
...
</Carousel>
</template>
```
<ComponentPreview name="CarouselPlugin" />
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

@ -0,0 +1,51 @@
<script setup lang="ts">
import { ref } from 'vue'
import { watchOnce } from '@vueuse/core'
import type { CarouselApi } from '@/lib/registry/default/ui/carousel'
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
const api = ref<CarouselApi>()
const totalCount = ref(0)
const current = ref(0)
function setApi(val: CarouselApi) {
api.value = val
}
watchOnce(api, (api) => {
if (!api)
return
totalCount.value = api.scrollSnapList().length
current.value = api.selectedScrollSnap() + 1
api.on('select', () => {
current.value = api.selectedScrollSnap() + 1
})
})
</script>
<template>
<div class="w-full sm:w-auto">
<Carousel class="relative w-full max-w-xs" @init-api="setApi">
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div class="py-2 text-center text-sm text-muted-foreground">
Slide {{ current }} of {{ totalCount }}
</div>
</div>
</template>

View File

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

View File

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

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import Autoplay from 'embla-carousel-autoplay'
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
const plugin = Autoplay({
delay: 2000,
stopOnMouseEnter: true,
stopOnInteraction: false,
})
</script>
<template>
<Carousel
class="relative w-full max-w-xs"
:plugins="[plugin]"
@mouseenter="plugin.stop"
@mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];"
>
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>

View File

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

View File

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

@ -1,15 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { Primitive, type PrimitiveProps } from 'radix-vue' import { Primitive, type PrimitiveProps } from 'radix-vue'
import { buttonVariants } from '.' import { buttonVariants } from '.'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
interface ButtonVariantProps extends VariantProps<typeof buttonVariants> {}
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
variant?: NonNullable<Parameters<typeof buttonVariants>[0]>['variant'] variant?: ButtonVariantProps['variant']
size?: NonNullable<Parameters<typeof buttonVariants>[0]>['size'] size?: ButtonVariantProps['size']
as?: string as?: string
} }
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
variant: 'default',
size: 'default',
as: 'button', as: 'button',
}) })
</script> </script>

View File

@ -3,7 +3,7 @@ import { cva } from 'class-variance-authority'
export { default as Button } from './Button.vue' export { default as Button } from './Button.vue'
export const buttonVariants = cva( export const buttonVariants = cva(
'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', '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',
{ {
variants: { variants: {
variant: { variant: {

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'
@ -64,11 +65,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>
@ -86,7 +97,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>
@ -118,8 +133,20 @@ onMounted(async () => {
.calendar .vc-day:has(.vc-highlights) { .calendar .vc-day:has(.vc-highlights) {
@apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden; @apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden;
} }
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) {
@apply bg-secondary rounded-md;
}
.calendar .vc-day:has(.vc-highlight-base-start) {
@apply rounded-l-md;
}
.calendar .vc-day:has(.vc-highlight-base-end) {
@apply rounded-r-md;
}
.calendar .vc-day:has(.vc-highlight-bg-outline):not(:has(.vc-highlight-base-start)):not(:has(.vc-highlight-base-end)) {
@apply rounded-md;
}
.calendar .vc-day-content { .calendar .vc-day-content {
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none; @apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
} }
.calendar .vc-day-content:not(.vc-highlight-content-light) { .calendar .vc-day-content:not(.vc-highlight-content-light) {
@apply rounded-md; @apply rounded-md;
@ -224,4 +251,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

@ -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 { ArrowRight } from 'lucide-vue-next'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
const props = defineProps<WithClassAsProps>()
const { orientation, canScrollNext, scrollNext } = useCarousel()
</script>
<template>
<Button
:disabled="!canScrollNext"
:class="cn(
'touch-manipulation absolute h-8 w-8 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>
<ArrowRight class="h-4 w-4 text-current" />
</slot>
</Button>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import { ArrowLeft } from 'lucide-vue-next'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
const props = defineProps<WithClassAsProps>()
const { orientation, canScrollPrev, scrollPrev } = useCarousel()
</script>
<template>
<Button
:disabled="!canScrollPrev"
:class="cn(
'touch-manipulation absolute h-8 w-8 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>
<ArrowLeft 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

@ -23,7 +23,7 @@ const props = defineProps<RadioGroupItemProps & { class?: string }>()
<RadioGroupIndicator <RadioGroupIndicator
:class="cn('flex items-center justify-center', props.class)" :class="cn('flex items-center justify-center', props.class)"
> >
<Circle class="w-2.5 h-2.5 text-foreground" /> <Circle class="w-2.5 h-2.5 text-current fill-current" />
</RadioGroupIndicator> </RadioGroupIndicator>
</RadioGroupItem> </RadioGroupItem>
</template> </template>

View File

@ -13,6 +13,7 @@ const props = withDefaults(
defineProps<SelectContentProps & { class?: string }>(), { defineProps<SelectContentProps & { class?: string }>(), {
position: 'popper', position: 'popper',
sideOffset: 4, sideOffset: 4,
avoidCollisions: true,
}, },
) )
const emits = defineEmits<SelectContentEmits>() const emits = defineEmits<SelectContentEmits>()
@ -26,7 +27,7 @@ const forwarded = useForwardPropsEmits(props, emits)
v-bind="{ ...forwarded, ...$attrs }" v-bind="{ ...forwarded, ...$attrs }"
:class=" :class="
cn( cn(
'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', '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]',
position === 'popper' position === 'popper'
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
props.class, props.class,

View File

@ -17,7 +17,7 @@ const props = withDefaults(
v-bind="props" v-bind="props"
:class="[ :class="[
cn( cn(
'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', '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',
props.class, props.class,
), ),
props.invalid props.invalid

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

@ -0,0 +1,51 @@
<script setup lang="ts">
import { ref } from 'vue'
import { watchOnce } from '@vueuse/core'
import type { CarouselApi } from '@/lib/registry/new-york/ui/carousel'
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/new-york/ui/carousel'
import { Card, CardContent } from '@/lib/registry/new-york/ui/card'
const api = ref<CarouselApi>()
const totalCount = ref(0)
const current = ref(0)
function setApi(val: CarouselApi) {
api.value = val
}
watchOnce(api, (api) => {
if (!api)
return
totalCount.value = api.scrollSnapList().length
current.value = api.selectedScrollSnap() + 1
api.on('select', () => {
current.value = api.selectedScrollSnap() + 1
})
})
</script>
<template>
<div class="w-full sm:w-auto">
<Carousel class="relative w-full max-w-xs" @init-api="setApi">
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div class="py-2 text-center text-sm text-muted-foreground">
Slide {{ current }} of {{ totalCount }}
</div>
</div>
</template>

View File

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

View File

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

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import Autoplay from 'embla-carousel-autoplay'
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/new-york/ui/carousel'
import { Card, CardContent } from '@/lib/registry/new-york/ui/card'
const plugin = Autoplay({
delay: 2000,
stopOnMouseEnter: true,
stopOnInteraction: false,
})
</script>
<template>
<Carousel
class="relative w-full max-w-xs"
:plugins="[plugin]"
@mouseenter="plugin.stop"
@mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];"
>
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>

View File

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

View File

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

@ -1,15 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { Primitive, type PrimitiveProps } from 'radix-vue' import { Primitive, type PrimitiveProps } from 'radix-vue'
import { buttonVariants } from '.' import { buttonVariants } from '.'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
interface ButtonVariantProps extends VariantProps<typeof buttonVariants> {}
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
variant?: NonNullable<Parameters<typeof buttonVariants>[0]>['variant'] variant?: ButtonVariantProps['variant']
size?: NonNullable<Parameters<typeof buttonVariants>[0]>['size'] size?: ButtonVariantProps['size']
as?: string as?: string
} }
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
variant: 'default',
size: 'default',
as: 'button', as: 'button',
}) })
</script> </script>

View File

@ -3,7 +3,7 @@ import { cva } from 'class-variance-authority'
export { default as Button } from './Button.vue' export { default as Button } from './Button.vue'
export const buttonVariants = cva( export const buttonVariants = cva(
'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', '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',
{ {
variants: { variants: {
variant: { variant: {

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 { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue' import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'
import { computed, nextTick, onMounted, ref } from 'vue' import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
import { isVCalendarSlot } from '.'
import { buttonVariants } from '@/lib/registry/new-york/ui/button' import { buttonVariants } from '@/lib/registry/new-york/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')">
<ChevronLeftIcon class="w-4 h-4" /> <ChevronLeftIcon 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>
@ -117,8 +132,20 @@ onMounted(async () => {
.calendar .vc-day:has(.vc-highlights) { .calendar .vc-day:has(.vc-highlights) {
@apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden; @apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden;
} }
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) {
@apply bg-secondary rounded-md;
}
.calendar .vc-day:has(.vc-highlight-base-start) {
@apply rounded-l-md;
}
.calendar .vc-day:has(.vc-highlight-base-end) {
@apply rounded-r-md;
}
.calendar .vc-day:has(.vc-highlight-bg-outline):not(:has(.vc-highlight-base-start)):not(:has(.vc-highlight-base-end)) {
@apply rounded-md;
}
.calendar .vc-day-content { .calendar .vc-day-content {
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-8 w-8 font-normal aria-selected:opacity-100 select-none; @apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-8 w-8 font-normal aria-selected:opacity-100 select-none;
} }
.calendar .vc-day-content:not(.vc-highlight-content-light) { .calendar .vc-day-content:not(.vc-highlight-content-light) {
@apply rounded-md; @apply rounded-md;
@ -230,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

@ -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 { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
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 { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
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 { ArrowRightIcon } from '@radix-icons/vue'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/new-york/ui/button'
const props = defineProps<WithClassAsProps>()
const { orientation, canScrollNext, scrollNext } = useCarousel()
</script>
<template>
<Button
:disabled="!canScrollNext"
:class="cn(
'touch-manipulation absolute h-8 w-8 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>
<ArrowRightIcon class="h-4 w-4 text-current" />
</slot>
</Button>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import { ArrowLeftIcon } from '@radix-icons/vue'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/new-york/ui/button'
const props = defineProps<WithClassAsProps>()
const { orientation, canScrollPrev, scrollPrev } = useCarousel()
</script>
<template>
<Button
:disabled="!canScrollPrev"
:class="cn(
'touch-manipulation absolute h-8 w-8 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>
<ArrowLeftIcon 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

@ -13,6 +13,7 @@ const props = withDefaults(
defineProps<SelectContentProps & { class?: string }>(), { defineProps<SelectContentProps & { class?: string }>(), {
position: 'popper', position: 'popper',
sideOffset: 4, sideOffset: 4,
avoidCollisions: true,
}, },
) )
const emits = defineEmits<SelectContentEmits>() const emits = defineEmits<SelectContentEmits>()
@ -26,7 +27,7 @@ const forwarded = useForwardPropsEmits(props, emits)
v-bind="{ ...forwarded, ...$attrs }" v-bind="{ ...forwarded, ...$attrs }"
:class=" :class="
cn( cn(
'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', '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]',
position === 'popper' position === 'popper'
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
props.class, props.class,

View File

@ -17,7 +17,7 @@ const props = withDefaults(
v-bind="props" v-bind="props"
:class="[ :class="[
cn( cn(
'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', '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',
props.class, props.class,
), ),
props.invalid props.invalid

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

@ -8,6 +8,7 @@ const DEPENDENCIES = new Map<string, string[]>([
['@vueuse/core', []], ['@vueuse/core', []],
['v-calendar', []], ['v-calendar', []],
['@tanstack/vue-table', []], ['@tanstack/vue-table', []],
['embla-carousel-vue', ['embla-carousel']],
['vee-validate', ['@vee-validate/zod', 'zod']], ['vee-validate', ['@vee-validate/zod', 'zod']],
]) ])
// Some dependencies latest tag were not compatible with Vue3. // Some dependencies latest tag were not compatible with Vue3.
@ -133,7 +134,7 @@ async function buildUIRegistry(componentPath: string, componentName: string) {
async function getDependencies(filename: string) { async function getDependencies(filename: string) {
const code = await readFile(filename, { encoding: 'utf8' }) const code = await readFile(filename, { encoding: 'utf8' })
const parsed = parse(code) const parsed = parse(code, { filename })
const registryDependencies = new Set<string>() const registryDependencies = new Set<string>()
const dependencies = new Set<string>() const dependencies = new Set<string>()

View File

@ -130,6 +130,28 @@
], ],
"type": "components:ui" "type": "components:ui"
}, },
{
"name": "carousel",
"dependencies": [
"embla-carousel-vue",
"embla-carousel"
],
"registryDependencies": [
"utils",
"button"
],
"files": [
"ui/carousel/Carousel.vue",
"ui/carousel/CarouselContent.vue",
"ui/carousel/CarouselItem.vue",
"ui/carousel/CarouselNext.vue",
"ui/carousel/CarouselPrevious.vue",
"ui/carousel/index.ts",
"ui/carousel/interface.ts",
"ui/carousel/useCarousel.ts"
],
"type": "components:ui"
},
{ {
"name": "checkbox", "name": "checkbox",
"dependencies": [], "dependencies": [],
@ -588,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

@ -0,0 +1,46 @@
{
"name": "carousel",
"dependencies": [
"embla-carousel-vue",
"embla-carousel"
],
"registryDependencies": [
"utils",
"button"
],
"files": [
{
"name": "Carousel.vue",
"content": "<script setup lang=\"ts\">\nimport emblaCarouselVue from 'embla-carousel-vue'\nimport { useProvideCarousel } from './useCarousel'\nimport type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {\n orientation: 'horizontal',\n})\n\nconst emits = defineEmits<CarouselEmits>()\n\nconst carouselArgs = useProvideCarousel(props, emits)\n\ndefineExpose(carouselArgs)\n\nfunction onKeyDown(event: KeyboardEvent) {\n const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'\n const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'\n\n if (event.key === prevKey) {\n event.preventDefault()\n carouselArgs.scrollPrev()\n\n return\n }\n\n if (event.key === nextKey) {\n event.preventDefault()\n carouselArgs.scrollNext()\n }\n}\n</script>\n\n<template>\n <div\n :class=\"cn('relative', props.class)\"\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabindex=\"0\"\n @keydown=\"onKeyDown\"\n >\n <slot v-bind=\"carouselArgs\" />\n </div>\n</template>\n"
},
{
"name": "CarouselContent.vue",
"content": "<script setup lang=\"ts\">\nimport type { WithClassAsProps } from './interface'\nimport { useCarousel } from './useCarousel'\nimport { cn } from '@/lib/utils'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { carouselRef, orientation } = useCarousel()\n</script>\n\n<template>\n <div ref=\"carouselRef\" class=\"overflow-hidden\">\n <div\n :class=\"\n cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n props.class,\n )\"\n v-bind=\"$attrs\"\n >\n <slot />\n </div>\n </div>\n</template>\n"
},
{
"name": "CarouselItem.vue",
"content": "<script setup lang=\"ts\">\nimport type { WithClassAsProps } from './interface'\nimport { useCarousel } from './useCarousel'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation } = useCarousel()\n</script>\n\n<template>\n <div\n role=\"group\"\n aria-roledescription=\"slide\"\n :class=\"cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n props.class,\n )\"\n >\n <slot />\n </div>\n</template>\n"
},
{
"name": "CarouselNext.vue",
"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",
"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",
"content": "export { default as Carousel } from './Carousel.vue'\nexport { default as CarouselContent } from './CarouselContent.vue'\nexport { default as CarouselItem } from './CarouselItem.vue'\nexport { default as CarouselPrevious } from './CarouselPrevious.vue'\nexport { default as CarouselNext } from './CarouselNext.vue'\nexport { useCarousel } from './useCarousel'\n\nexport type {\n EmblaCarouselType as CarouselApi,\n} from 'embla-carousel'\n"
},
{
"name": "interface.ts",
"content": "import type {\n EmblaCarouselType as CarouselApi,\n EmblaOptionsType as CarouselOptions,\n EmblaPluginType as CarouselPlugin,\n} from 'embla-carousel'\nimport type { HTMLAttributes, Ref } from 'vue'\n\nexport interface CarouselProps {\n opts?: CarouselOptions | Ref<CarouselOptions>\n plugins?: CarouselPlugin[] | Ref<CarouselPlugin[]>\n orientation?: 'horizontal' | 'vertical'\n}\n\nexport interface CarouselEmits {\n (e: 'init-api', payload: CarouselApi): void\n}\n\nexport interface WithClassAsProps {\n class?: HTMLAttributes['class']\n}\n"
},
{
"name": "useCarousel.ts",
"content": "import { createInjectionState } from '@vueuse/core'\nimport emblaCarouselVue from 'embla-carousel-vue'\nimport { onMounted, ref } from 'vue'\nimport type {\n EmblaCarouselType as CarouselApi,\n} from 'embla-carousel'\nimport type { CarouselEmits, CarouselProps } from './interface'\n\nconst [useProvideCarousel, useInjectCarousel] = createInjectionState(\n ({\n opts, orientation, plugins,\n }: CarouselProps, emits: CarouselEmits) => {\n const [emblaNode, emblaApi] = emblaCarouselVue({\n ...opts,\n axis: orientation === 'horizontal' ? 'x' : 'y',\n }, plugins)\n\n function scrollPrev() {\n emblaApi.value?.scrollPrev()\n }\n function scrollNext() {\n emblaApi.value?.scrollNext()\n }\n\n const canScrollNext = ref(true)\n const canScrollPrev = ref(true)\n\n function onSelect(api: CarouselApi) {\n canScrollNext.value = api.canScrollNext()\n canScrollPrev.value = api.canScrollPrev()\n }\n\n onMounted(() => {\n if (!emblaApi.value)\n return\n\n emblaApi.value?.on('init', onSelect)\n emblaApi.value?.on('reInit', onSelect)\n emblaApi.value?.on('select', onSelect)\n\n emits('init-api', emblaApi.value)\n })\n\n return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation }\n },\n)\n\nfunction useCarousel() {\n const carouselState = useInjectCarousel()\n\n if (!carouselState)\n throw new Error('useCarousel must be used within a <Carousel />')\n\n return carouselState\n}\n\nexport { useCarousel, useProvideCarousel }\n"
}
],
"type": "components:ui"
}

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

@ -0,0 +1,46 @@
{
"name": "carousel",
"dependencies": [
"embla-carousel-vue",
"embla-carousel"
],
"registryDependencies": [
"utils",
"button"
],
"files": [
{
"name": "Carousel.vue",
"content": "<script setup lang=\"ts\">\nimport emblaCarouselVue from 'embla-carousel-vue'\nimport { useProvideCarousel } from './useCarousel'\nimport type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {\n orientation: 'horizontal',\n})\n\nconst emits = defineEmits<CarouselEmits>()\n\nconst carouselArgs = useProvideCarousel(props, emits)\n\ndefineExpose(carouselArgs)\n\nfunction onKeyDown(event: KeyboardEvent) {\n const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'\n const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'\n\n if (event.key === prevKey) {\n event.preventDefault()\n carouselArgs.scrollPrev()\n\n return\n }\n\n if (event.key === nextKey) {\n event.preventDefault()\n carouselArgs.scrollNext()\n }\n}\n</script>\n\n<template>\n <div\n :class=\"cn('relative', props.class)\"\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabindex=\"0\"\n @keydown=\"onKeyDown\"\n >\n <slot v-bind=\"carouselArgs\" />\n </div>\n</template>\n"
},
{
"name": "CarouselContent.vue",
"content": "<script setup lang=\"ts\">\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { carouselRef, orientation } = useCarousel()\n</script>\n\n<template>\n <div ref=\"carouselRef\" class=\"overflow-hidden\">\n <div\n :class=\"\n cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n props.class,\n )\"\n v-bind=\"$attrs\"\n >\n <slot />\n </div>\n </div>\n</template>\n"
},
{
"name": "CarouselItem.vue",
"content": "<script setup lang=\"ts\">\nimport { useCarousel } from './useCarousel'\nimport type { WithClassAsProps } from './interface'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<WithClassAsProps>()\n\nconst { orientation } = useCarousel()\n</script>\n\n<template>\n <div\n role=\"group\"\n aria-roledescription=\"slide\"\n :class=\"cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n props.class,\n )\"\n >\n <slot />\n </div>\n</template>\n"
},
{
"name": "CarouselNext.vue",
"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",
"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",
"content": "export { default as Carousel } from './Carousel.vue'\nexport { default as CarouselContent } from './CarouselContent.vue'\nexport { default as CarouselItem } from './CarouselItem.vue'\nexport { default as CarouselPrevious } from './CarouselPrevious.vue'\nexport { default as CarouselNext } from './CarouselNext.vue'\nexport { useCarousel } from './useCarousel'\n\nexport type {\n EmblaCarouselType as CarouselApi,\n} from 'embla-carousel'\n"
},
{
"name": "interface.ts",
"content": "import type {\n EmblaCarouselType as CarouselApi,\n EmblaOptionsType as CarouselOptions,\n EmblaPluginType as CarouselPlugin,\n} from 'embla-carousel'\nimport type { HTMLAttributes, Ref } from 'vue'\n\nexport interface CarouselProps {\n opts?: CarouselOptions | Ref<CarouselOptions>\n plugins?: CarouselPlugin[] | Ref<CarouselPlugin[]>\n orientation?: 'horizontal' | 'vertical'\n}\n\nexport interface CarouselEmits {\n (e: 'init-api', payload: CarouselApi): void\n}\n\nexport interface WithClassAsProps {\n class?: HTMLAttributes['class']\n}\n"
},
{
"name": "useCarousel.ts",
"content": "import { createInjectionState } from '@vueuse/core'\nimport emblaCarouselVue from 'embla-carousel-vue'\nimport { onMounted, ref } from 'vue'\nimport type {\n EmblaCarouselType as CarouselApi,\n} from 'embla-carousel'\nimport type { CarouselEmits, CarouselProps } from './interface'\n\nconst [useProvideCarousel, useInjectCarousel] = createInjectionState(\n ({\n opts, orientation, plugins,\n }: CarouselProps, emits: CarouselEmits) => {\n const [emblaNode, emblaApi] = emblaCarouselVue({\n ...opts,\n axis: orientation === 'horizontal' ? 'x' : 'y',\n }, plugins)\n\n function scrollPrev() {\n emblaApi.value?.scrollPrev()\n }\n function scrollNext() {\n emblaApi.value?.scrollNext()\n }\n\n const canScrollNext = ref(true)\n const canScrollPrev = ref(true)\n\n function onSelect(api: CarouselApi) {\n canScrollNext.value = api.canScrollNext()\n canScrollPrev.value = api.canScrollPrev()\n }\n\n onMounted(() => {\n if (!emblaApi.value)\n return\n\n emblaApi.value?.on('init', onSelect)\n emblaApi.value?.on('reInit', onSelect)\n emblaApi.value?.on('select', onSelect)\n\n emits('init-api', emblaApi.value)\n })\n\n return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation }\n },\n)\n\nfunction useCarousel() {\n const carouselState = useInjectCarousel()\n\n if (!carouselState)\n throw new Error('useCarousel must be used within a <Carousel />')\n\n return carouselState\n}\n\nexport { useCarousel, useProvideCarousel }\n"
}
],
"type": "components:ui"
}

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

Some files were not shown because too many files have changed in this diff Show More