Merge remote-tracking branch 'origin/dev' into charting

This commit is contained in:
zernonia 2023-12-05 09:52:06 +08:00
commit a84e1ece5c
77 changed files with 6447 additions and 1158 deletions

View File

@ -17,6 +17,9 @@ export default defineConfig({
['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }],
['link', { rel: 'manifest', href: '/site.webmanifest' }],
['meta', { name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' }],
['meta', { name: 'theme-color', media: '(prefers-color-scheme: dark)', content: 'black' }],
['meta', { name: 'creator', content: 'radix-vue' }],
['meta', { name: 'theme-color', content: '#41b883' }],
['meta', { name: 'og:type', content: 'website' }],

View File

@ -4,7 +4,6 @@ import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import ExamplesNav from '../components/ExamplesNav.vue'
import { announcementConfig } from '../config/site'
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
import GitHubIcon from '~icons/radix-icons/github-logo'
import { buttonVariants } from '@/lib/registry/new-york/ui/button'

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import { Button } from '@/lib/registry/default/ui/button'
import {
Tooltip,
TooltipContent,

View File

@ -44,14 +44,14 @@ const { theme, setTheme } = useConfigStore()
class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-border text-xs"
:class="
color === theme
? 'border-foreground'
? 'border-primary'
: 'border-transparent'
"
@click="setTheme(color)"
>
<span
class="flex h-6 w-6 items-center justify-center rounded-full"
:style="{ backgroundColor: colors[color][7].rgb }"
:style="{ backgroundColor: colors[color][6].rgb }"
>
<RadixIconsCheck
v-if="color === theme"
@ -63,7 +63,7 @@ const { theme, setTheme } = useConfigStore()
<TooltipContent
align="center"
:side-offset="1"
class="capitalize"
class="capitalize bg-zinc-900 text-zinc-50"
>
{{ allColors[index] }}
</TooltipContent>

View File

@ -1,3 +1,11 @@
html {
color-scheme: light;
}
html.dark {
color-scheme: dark;
}
.theme-zinc {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;

View File

@ -7,7 +7,7 @@ import cssRaw from '../../../../../packages/cli/test/fixtures/nuxt/assets/css/ta
import { type Style } from '@/lib/registry/styles'
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
let files = {}
let files: Record<string, any> = {}
files = constructFiles(componentName, style, sources)
files['.codesandbox/Dockerfile'] = {
content: 'FROM node:18',

View File

@ -1,7 +1,7 @@
{
"name": "www",
"type": "module",
"version": "0.8.2",
"version": "0.8.4",
"files": [
"dist"
],
@ -9,7 +9,9 @@
"dev": "vitepress dev",
"build": "vitepress build",
"preview": "vitepress preview",
"build:registry": "tsx ./scripts/build-registry.ts"
"typecheck": "vue-tsc --noEmit",
"typecheck:registry": "vue-tsc --noEmit -p tsconfig.registry.json",
"build:registry": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts"
},
"dependencies": {
"@formkit/auto-animate": "^0.8.0",
@ -26,7 +28,7 @@
"codesandbox": "^2.2.3",
"date-fns": "^2.30.0",
"lucide-vue-next": "^0.276.0",
"radix-vue": "^1.1.1",
"radix-vue": "^1.2.3",
"tailwindcss-animate": "^1.0.7",
"v-calendar": "^3.1.2",
"vee-validate": "4.11.8",
@ -45,9 +47,10 @@
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/compiler-core": "^3.3.7",
"@vue/compiler-dom": "^3.3.7",
"@vue/tsconfig": "^0.4.0",
"autoprefixer": "^10.4.16",
"lodash.template": "^4.5.0",
"radix-vue": "^1.1.1",
"pathe": "^1.1.1",
"rimraf": "^5.0.5",
"tailwind-merge": "^2.0.0",
"tailwindcss": "^3.3.5",

View File

@ -11,10 +11,45 @@ primitive: https://www.radix-vue.com/components/collapsible.html
## Installation
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add collapsible
```
### Update `tailwind.config.js`
Add the following animations to your `tailwind.config.js` file:
```js title="tailwind.config.js" {5-18}
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
extend: {
keyframes: {
'collapsible-down': {
from: { height: 0 },
to: { height: 'var(--radix-collapsible-content-height)' },
},
'collapsible-up': {
from: { height: 'var(--radix-collapsible-content-height)' },
to: { height: 0 },
},
},
animation: {
'collapsible-down': 'collapsible-down 0.2s ease-in-out',
'collapsible-up': 'collapsible-up 0.2s ease-in-out',
},
},
},
}
```
</Steps>
## Usage
```vue

View File

@ -6,6 +6,14 @@ component: true
<ComponentPreview name="ComboboxDemo" />
<br>
<Callout title="Note" class="bg-destructive">
[Radix Vue](https://github.com/radix-vue/radix-vue/releases/tag/v1.2.0) introduced a breaking change. You will need to wrap `ComboboxGroup` and `ComboboxItem` inside of `ComboboxList` now.
</Callout>
## Installation
The Combobox is built using a composition of the `<Popover />` and the `<Command />` components.
@ -27,6 +35,7 @@ import {
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from '@/components/ui/command'
import {
Popover,
@ -64,22 +73,24 @@ const value = ref({})
<Command v-model="value">
<CommandInput placeholder="Search framework..." />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
<CommandItem
v-for="framework in frameworks"
:key="framework.value"
:value="framework"
@select="open = false"
>
<Check
:class="cn(
'mr-2 h-4 w-4',
value === framework.value ? 'opacity-100' : 'opacity-0',
)"
/>
{{ framework.label }}
</CommandItem>
</CommandGroup>
<CommandList>
<CommandGroup>
<CommandItem
v-for="framework in frameworks"
:key="framework.value"
:value="framework"
@select="open = false"
>
<Check
:class="cn(
'mr-2 h-4 w-4',
value === framework.value ? 'opacity-100' : 'opacity-0',
)"
/>
{{ framework.label }}
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>

View File

@ -76,7 +76,7 @@ To activate the `Dialog` component from within a `Context Menu` or `Dropdown Men
</ContextMenu>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure absolutely sure?</DialogTitle>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. Are you sure you want to permanently
delete this file from our servers?

View File

@ -209,7 +209,7 @@ const onSubmit = form.handleSubmit((values) => {
<template>
<form @submit="onSubmit">
...
</Form>
</form>
</template>
```
@ -302,7 +302,7 @@ const onSubmit = form.handleSubmit((values) => {
<Button type="submit">
Submit
</Button>
</Form>
</form>
</template>
```

View File

@ -26,31 +26,28 @@ npm install -D typescript
npm install -D @nuxtjs/tailwindcss
```
### Install `shadcn-nuxt` module (New ✨)
```bash
npm install -D shadcn-nuxt
```
### Configure `nuxt.config.ts`
<Callout class="mt-4">
**Tip:** It's better to use Nuxt `components:dirs` hook to extend auto-import components directories.
If you use `components` key in `nuxt.config.ts` default config will disposed
</Callout>
```ts
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss'],
hooks: {
'components:dirs': (dirs) => {
dirs.unshift({
path: '~/components/ui',
// this is required else Nuxt will autoImport `.ts` file
extensions: ['.vue'],
// prefix for your components, eg: UiButton
prefix: 'Ui',
// prevent adding another prefix component by it's path.
pathPrefix: false
})
}
modules: ['@nuxtjs/tailwindcss', 'shadcn-nuxt'],
shadcn: {
/**
* Prefix for all the imported component
*/
prefix: '',
/**
* Directory that the component lives in.
* @default "./components/ui"
*/
componentDir: './components/ui'
}
})
```
@ -133,7 +130,7 @@ The command above will add the `Button` component to your project. Nuxt autoImpo
```vue {3}
<template>
<div>
<UiButton>Click me</UiButton>
<Button>Click me</Button>
</div>
</template>
```

View File

@ -131,7 +131,7 @@ const selectedTeam = ref<Team>(groups[0].teams[0])
<CommandGroup>
<DialogTrigger as-child>
<CommandItem
:value="{ label: 'Create Team' }"
value="create-team"
@select="() => {
open = false
showNewTeamDialog = true

View File

@ -3,7 +3,6 @@ import { h, ref } from 'vue'
import * as z from 'zod'
import { format } from 'date-fns'
import { toTypedSchema } from '@vee-validate/zod'
import { configure } from 'vee-validate'
import { Check, ChevronsUpDown } from 'lucide-vue-next'
import { cn } from '@/lib/utils'

View File

@ -7,7 +7,7 @@ import { Cross1Icon } from '@radix-icons/vue'
import { cn } from '@/lib/utils'
import { Input } from '@/lib/registry/new-york/ui/input'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
import {

View File

@ -236,7 +236,7 @@ import CounterClockwiseClockIcon from '~icons/radix-icons/counter-clockwise-cloc
</TabsTrigger>
</TabsList>
</div>
<ModelSelector :types="types" :models="models" />
<ModelSelector />
<TemperatureSelector :default-value="[0.56]" />
<MaxLengthSelector :default-value="[256]" />
<TopPSelector :default-value="[0.9]" />

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Column } from '@tanstack/vue-table'
import type { Component } from 'vue'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { type Task } from '../data/schema'
import PlusCircledIcon from '~icons/radix-icons/plus-circled'
import CheckIcon from '~icons/radix-icons/check'

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ChevronDown, Minus, Plus, Send } from 'lucide-vue-next'
import { Minus, Plus } from 'lucide-vue-next'
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
import { useData } from 'vitepress'
import { Button } from '@/lib/registry/default/ui/button'

View File

@ -10,6 +10,7 @@ import {
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/lib/registry/default/ui/command'
import {
Popover,
@ -48,25 +49,28 @@ const filterFunction = (list: typeof frameworks, search: string) => list.filter(
<Command :filter-function="filterFunction">
<CommandInput placeholder="Search framework..." />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
<CommandItem
v-for="framework in frameworks"
:key="framework.value"
:value="framework"
@select="(ev) => {
value = ev.detail.value as typeof framework
open = false
}"
>
<Check
:class="cn(
'mr-2 h-4 w-4',
value?.value === framework.value ? 'opacity-100' : 'opacity-0',
)"
/>
{{ framework.label }}
</CommandItem>
</CommandGroup>
<CommandList>
<CommandGroup>
<CommandItem
v-for="framework in frameworks"
:key="framework.value"
:value="framework"
@select="(ev) => {
value = ev.detail.value
console.log(ev)
open = false
}"
>
<Check
:class="cn(
'mr-2 h-4 w-4',
value?.value === framework.value ? 'opacity-100' : 'opacity-0',
)"
/>
{{ framework.label }}
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>

View File

@ -13,6 +13,7 @@ import {
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/lib/registry/default/ui/command'
import {
FormControl,
@ -83,23 +84,25 @@ const onSubmit = handleSubmit((values) => {
<Command>
<CommandInput placeholder="Search language..." />
<CommandEmpty>Nothing found.</CommandEmpty>
<CommandGroup>
<CommandItem
v-for="language in languages"
:key="language.value"
:value="language.label"
@select="() => {
setValues({
language: language.value,
})
}"
>
<Check
:class="cn('mr-2 h-4 w-4', language.value === values.language ? 'opacity-100' : 'opacity-0')"
/>
{{ language.label }}
</CommandItem>
</CommandGroup>
<CommandList>
<CommandGroup>
<CommandItem
v-for="language in languages"
:key="language.value"
:value="language.label"
@select="() => {
setValues({
language: language.value,
})
}"
>
<Check
:class="cn('mr-2 h-4 w-4', language.value === values.language ? 'opacity-100' : 'opacity-0')"
/>
{{ language.label }}
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { h, ref } from 'vue'
import { ref } from 'vue'
import {
ArrowUpCircle,
CheckCircle2,

View File

@ -2,20 +2,24 @@
import { ScrollArea, ScrollBar } from '@/lib/registry/default/ui/scroll-area'
interface Artwork {
id: string
artist: string
art: string
}
const works: Artwork[] = [
{
id: '1',
artist: 'Ornella Binni',
art: 'https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80',
},
{
id: '2',
artist: 'Tom Byrom',
art: 'https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80',
},
{
id: '3',
artist: 'Vladimir Malyavko',
art: 'https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80',
},
@ -30,7 +34,7 @@ const works: Artwork[] = [
<div class="overflow-hidden rounded-md">
<img
:src="artwork.art"
:alt="`Photo by ${artwork.name}`"
:alt="`Photo by ${artwork.artist}`"
class="aspect-[3/4] w-36 h-56 object-cover"
>
</div>

View File

@ -7,15 +7,30 @@ import { computed, nextTick, onMounted, ref } from 'vue'
import { buttonVariants } from '@/lib/registry/default/ui/button'
import { cn } from '@/lib/utils'
/* Extracted from v-calendar */
type DatePickerModel = DatePickerDate | DatePickerRangeObject
type DateSource = Date | string | number
type DatePickerDate = DateSource | Partial<SimpleDateParts> | null
interface DatePickerRangeObject {
start: Exclude<DatePickerDate, null>
end: Exclude<DatePickerDate, null>
}
interface SimpleDateParts {
year: number
month: number
day: number
hours: number
minutes: number
seconds: number
milliseconds: number
}
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<{
modelValue?: string | number | Date | Partial<{
start: Date
end: Date
}>
modelValue?: string | number | Date | DatePickerModel
modelModifiers?: object
columns?: number
type?: 'single' | 'range'
@ -53,7 +68,7 @@ onMounted(async () => {
<template>
<div class="relative">
<div class="absolute top-3 flex justify-between w-full px-4">
<div 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')">
<ChevronLeft class="w-4 h-4" />
</button>
@ -119,4 +134,94 @@ onMounted(async () => {
.calendar .vc-highlight-content-light {
@apply bg-accent text-accent-foreground;
}
.calendar .vc-pane-container.in-transition {
@apply overflow-hidden;
}
.calendar .vc-pane-container {
@apply w-full relative;
}
:root {
--vc-slide-translate: 22px;
--vc-slide-duration: 0.15s;
--vc-slide-timing: ease;
}
.calendar .vc-fade-enter-active,
.calendar .vc-fade-leave-active,
.calendar .vc-slide-left-enter-active,
.calendar .vc-slide-left-leave-active,
.calendar .vc-slide-right-enter-active,
.calendar .vc-slide-right-leave-active,
.calendar .vc-slide-up-enter-active,
.calendar .vc-slide-up-leave-active,
.calendar .vc-slide-down-enter-active,
.calendar .vc-slide-down-leave-active,
.calendar .vc-slide-fade-enter-active,
.calendar .vc-slide-fade-leave-active {
transition:
opacity var(--vc-slide-duration) var(--vc-slide-timing),
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
transition:
transform var(--vc-slide-duration) var(--vc-slide-timing),
opacity var(--vc-slide-duration) var(--vc-slide-timing);
transition:
transform var(--vc-slide-duration) var(--vc-slide-timing),
opacity var(--vc-slide-duration) var(--vc-slide-timing),
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
pointer-events: none;
}
.calendar .vc-none-leave-active,
.calendar .vc-fade-leave-active,
.calendar .vc-slide-left-leave-active,
.calendar .vc-slide-right-leave-active,
.calendar .vc-slide-up-leave-active,
.calendar .vc-slide-down-leave-active {
position: absolute !important;
width: 100%;
}
.calendar .vc-none-enter-from,
.calendar .vc-none-leave-to,
.calendar .vc-fade-enter-from,
.calendar .vc-fade-leave-to,
.calendar .vc-slide-left-enter-from,
.calendar .vc-slide-left-leave-to,
.calendar .vc-slide-right-enter-from,
.calendar .vc-slide-right-leave-to,
.calendar .vc-slide-up-enter-from,
.calendar .vc-slide-up-leave-to,
.calendar .vc-slide-down-enter-from,
.calendar .vc-slide-down-leave-to,
.calendar .vc-slide-fade-enter-from,
.calendar .vc-slide-fade-leave-to {
opacity: 0;
}
.calendar .vc-slide-left-enter-from,
.calendar .vc-slide-right-leave-to,
.calendar .vc-slide-fade-enter-from.direction-left,
.calendar .vc-slide-fade-leave-to.direction-left {
-webkit-transform: translateX(var(--vc-slide-translate));
transform: translateX(var(--vc-slide-translate));
}
.calendar .vc-slide-right-enter-from,
.calendar .vc-slide-left-leave-to,
.calendar .vc-slide-fade-enter-from.direction-right,
.calendar .vc-slide-fade-leave-to.direction-right {
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
transform: translateX(calc(-1 * var(--vc-slide-translate)));
}
.calendar .vc-slide-up-enter-from,
.calendar .vc-slide-down-leave-to,
.calendar .vc-slide-fade-enter-from.direction-top,
.calendar .vc-slide-fade-leave-to.direction-top {
-webkit-transform: translateY(var(--vc-slide-translate));
transform: translateY(var(--vc-slide-translate));
}
.calendar .vc-slide-down-enter-from,
.calendar .vc-slide-up-leave-to,
.calendar .vc-slide-fade-enter-from.direction-bottom,
.calendar .vc-slide-fade-leave-to.direction-bottom {
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
transform: translateY(calc(-1 * var(--vc-slide-translate)));
}
</style>

View File

@ -17,7 +17,7 @@ const forwarded = useForwardPropsEmits(props, emits)
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
$attrs.class ?? '')"
>
<CheckboxIndicator class="flex items-center justify-center text-current">
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
<Check class="h-4 w-4" />
</CheckboxIndicator>
</CheckboxRoot>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useEmitAsProps, useForwardPropsEmits } from 'radix-vue'
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<DropdownMenuRootProps>()
const emits = defineEmits<DropdownMenuRootEmits>()

View File

@ -1,7 +1,12 @@
<script setup lang="ts">
import { useAttrs } from 'vue'
import { useVModel } from '@vueuse/core'
import { cn } from '@/lib/utils'
defineOptions({
inheritAttrs: false,
})
const props = defineProps<{
defaultValue?: string | number
modelValue?: string | number
@ -11,6 +16,8 @@ const emits = defineEmits<{
(e: 'update:modelValue', payload: string | number): void
}>()
const { class: className, ...rest } = useAttrs()
const modelValue = useVModel(props, 'modelValue', emits, {
passive: true,
defaultValue: props.defaultValue,
@ -18,5 +25,5 @@ const modelValue = useVModel(props, 'modelValue', emits, {
</script>
<template>
<input v-model="modelValue" type="text" :class="cn('flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', $attrs.class ?? '')">
<input v-model="modelValue" :class="cn('flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', className ?? '')" v-bind="rest">
</template>

View File

@ -10,6 +10,7 @@ import {
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/lib/registry/new-york/ui/command'
import {
Popover,
@ -48,25 +49,27 @@ const filterFunction = (list: typeof frameworks, search: string) => list.filter(
<Command v-model="value" :filter-function="filterFunction">
<CommandInput class="h-9" placeholder="Search framework..." />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
<CommandItem
v-for="framework in frameworks"
:key="framework.value"
:value="framework"
@select="(ev) => {
value = ev.detail.value as typeof framework
open = false
}"
>
{{ framework.label }}
<CheckIcon
:class="cn(
'ml-auto h-4 w-4',
value?.value === framework.value ? 'opacity-100' : 'opacity-0',
)"
/>
</CommandItem>
</CommandGroup>
<CommandList>
<CommandGroup>
<CommandItem
v-for="framework in frameworks"
:key="framework.value"
:value="framework"
@select="(ev) => {
value = ev.detail.value
open = false
}"
>
{{ framework.label }}
<CheckIcon
:class="cn(
'ml-auto h-4 w-4',
value?.value === framework.value ? 'opacity-100' : 'opacity-0',
)"
/>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>

View File

@ -13,6 +13,7 @@ import {
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/lib/registry/new-york/ui/command'
import {
FormControl,
@ -86,23 +87,25 @@ const onSubmit = handleSubmit((values) => {
<Command>
<CommandInput placeholder="Search language..." />
<CommandEmpty>Nothing found.</CommandEmpty>
<CommandGroup>
<CommandItem
v-for="language in languages"
:key="language.value"
:value="language.label"
@select="() => {
setValues({
language: language.value,
})
}"
>
{{ language.label }}
<CheckIcon
:class="cn('ml-auto h-4 w-4', language.value === values.language ? 'opacity-100' : 'opacity-0')"
/>
</CommandItem>
</CommandGroup>
<CommandList>
<CommandGroup>
<CommandItem
v-for="language in languages"
:key="language.value"
:value="language.label"
@select="() => {
setValues({
language: language.value,
})
}"
>
{{ language.label }}
<CheckIcon
:class="cn('ml-auto h-4 w-4', language.value === values.language ? 'opacity-100' : 'opacity-0')"
/>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>

View File

@ -2,20 +2,24 @@
import { ScrollArea, ScrollBar } from '@/lib/registry/new-york/ui/scroll-area'
interface Artwork {
id: string
artist: string
art: string
}
const works: Artwork[] = [
{
id: '1',
artist: 'Ornella Binni',
art: 'https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80',
},
{
id: '2',
artist: 'Tom Byrom',
art: 'https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80',
},
{
id: '3',
artist: 'Vladimir Malyavko',
art: 'https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80',
},
@ -30,7 +34,7 @@ const works: Artwork[] = [
<div class="overflow-hidden rounded-md">
<img
:src="artwork.art"
:alt="`Photo by ${artwork.name}`"
:alt="`Photo by ${artwork.artist}`"
class="aspect-[3/4] w-36 h-56 object-cover"
>
</div>

View File

@ -7,15 +7,29 @@ import { computed, nextTick, onMounted, ref } from 'vue'
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
import { cn } from '@/lib/utils'
/* Extracted from v-calendar */
type DatePickerModel = DatePickerDate | DatePickerRangeObject
type DateSource = Date | string | number
type DatePickerDate = DateSource | Partial<SimpleDateParts> | null
interface DatePickerRangeObject {
start: Exclude<DatePickerDate, null>
end: Exclude<DatePickerDate, null>
}
interface SimpleDateParts {
year: number
month: number
day: number
hours: number
minutes: number
seconds: number
milliseconds: number
}
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps< {
modelValue?: string | number | Date | Partial<{
start: Date
end: Date
}>
modelValue?: string | number | Date | DatePickerModel
modelModifiers?: object
columns?: number
type?: 'single' | 'range'
@ -53,7 +67,7 @@ onMounted(async () => {
<template>
<div class="relative">
<div class="absolute top-3 flex justify-between w-full px-4">
<div 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')">
<ChevronLeftIcon class="w-4 h-4" />
</button>
@ -119,4 +133,101 @@ onMounted(async () => {
.calendar .vc-highlight-content-light {
@apply bg-accent text-accent-foreground;
}
.calendar .vc-pane-container.in-transition {
@apply overflow-hidden;
}
.calendar .vc-pane-container {
@apply w-full relative;
}
:root {
--vc-slide-translate: 22px;
--vc-slide-duration: 0.15s;
--vc-slide-timing: ease;
}
.calendar .vc-fade-enter-active,
.calendar .vc-fade-leave-active,
.calendar .vc-slide-left-enter-active,
.calendar .vc-slide-left-leave-active,
.calendar .vc-slide-right-enter-active,
.calendar .vc-slide-right-leave-active,
.calendar .vc-slide-up-enter-active,
.calendar .vc-slide-up-leave-active,
.calendar .vc-slide-down-enter-active,
.calendar .vc-slide-down-leave-active,
.calendar .vc-slide-fade-enter-active,
.calendar .vc-slide-fade-leave-active {
transition:
opacity var(--vc-slide-duration) var(--vc-slide-timing),
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
transition:
transform var(--vc-slide-duration) var(--vc-slide-timing),
opacity var(--vc-slide-duration) var(--vc-slide-timing);
transition:
transform var(--vc-slide-duration) var(--vc-slide-timing),
opacity var(--vc-slide-duration) var(--vc-slide-timing),
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
pointer-events: none;
}
.calendar .vc-none-leave-active,
.calendar .vc-fade-leave-active,
.calendar .vc-slide-left-leave-active,
.calendar .vc-slide-right-leave-active,
.calendar .vc-slide-up-leave-active,
.calendar .vc-slide-down-leave-active {
position: absolute !important;
width: 100%;
}
.calendar .vc-none-enter-from,
.calendar .vc-none-leave-to,
.calendar .vc-fade-enter-from,
.calendar .vc-fade-leave-to,
.calendar .vc-slide-left-enter-from,
.calendar .vc-slide-left-leave-to,
.calendar .vc-slide-right-enter-from,
.calendar .vc-slide-right-leave-to,
.calendar .vc-slide-up-enter-from,
.calendar .vc-slide-up-leave-to,
.calendar .vc-slide-down-enter-from,
.calendar .vc-slide-down-leave-to,
.calendar .vc-slide-fade-enter-from,
.calendar .vc-slide-fade-leave-to {
opacity: 0;
}
.calendar .vc-slide-left-enter-from,
.calendar .vc-slide-right-leave-to,
.calendar .vc-slide-fade-enter-from.direction-left,
.calendar .vc-slide-fade-leave-to.direction-left {
-webkit-transform: translateX(var(--vc-slide-translate));
transform: translateX(var(--vc-slide-translate));
}
.calendar .vc-slide-right-enter-from,
.calendar .vc-slide-left-leave-to,
.calendar .vc-slide-fade-enter-from.direction-right,
.calendar .vc-slide-fade-leave-to.direction-right {
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
transform: translateX(calc(-1 * var(--vc-slide-translate)));
}
.calendar .vc-slide-up-enter-from,
.calendar .vc-slide-down-leave-to,
.calendar .vc-slide-fade-enter-from.direction-top,
.calendar .vc-slide-fade-leave-to.direction-top {
-webkit-transform: translateY(var(--vc-slide-translate));
transform: translateY(var(--vc-slide-translate));
}
.calendar .vc-slide-down-enter-from,
.calendar .vc-slide-up-leave-to,
.calendar .vc-slide-fade-enter-from.direction-bottom,
.calendar .vc-slide-fade-leave-to.direction-bottom {
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
transform: translateY(calc(-1 * var(--vc-slide-translate)));
}
</style>

View File

@ -17,7 +17,7 @@ const forwarded = useForwardPropsEmits(props, emits)
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
$attrs.class ?? '')"
>
<CheckboxIndicator class="flex items-center justify-center text-current">
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
<CheckIcon class="h-4 w-4" />
</CheckboxIndicator>
</CheckboxRoot>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ComboboxRootEmits, ComboboxRootProps } from 'radix-vue'
import { ComboboxRoot, useEmitAsProps, useForwardPropsEmits } from 'radix-vue'
import { ComboboxRoot, useForwardPropsEmits } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxRootProps>()

View File

@ -11,6 +11,8 @@ const forwarded = useForwardPropsEmits(props, emits)
<template>
<ComboboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', $attrs.class ?? '')">
<slot />
<div role="presentation">
<slot />
</div>
</ComboboxContent>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useEmitAsProps, useForwardPropsEmits } from 'radix-vue'
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<DropdownMenuRootProps>()
const emits = defineEmits<DropdownMenuRootEmits>()

View File

@ -1,7 +1,12 @@
<script setup lang="ts">
import { useAttrs } from 'vue'
import { useVModel } from '@vueuse/core'
import { cn } from '@/lib/utils'
defineOptions({
inheritAttrs: false,
})
const props = defineProps<{
defaultValue?: string | number
modelValue?: string | number
@ -11,6 +16,8 @@ const emits = defineEmits<{
(e: 'update:modelValue', payload: string | number): void
}>()
const { class: className, ...rest } = useAttrs()
const modelValue = useVModel(props, 'modelValue', emits, {
passive: true,
defaultValue: props.defaultValue,
@ -18,5 +25,5 @@ const modelValue = useVModel(props, 'modelValue', emits, {
</script>
<template>
<input v-model="modelValue" type="text" :class="cn('flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', $attrs.class ?? '')">
<input v-model="modelValue" :class="cn('flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', className ?? '')" v-bind="rest">
</template>

View File

@ -1,5 +1,5 @@
import { readFile, readdir } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { join, resolve } from 'pathe'
import { compileScript, parse } from 'vue/compiler-sfc'
import type { Registry } from '../../lib/registry'

View File

@ -1,7 +1,7 @@
import type { Updater } from '@tanstack/vue-table'
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { type Ref, camelize, getCurrentInstance, toHandlerKey } from 'vue'
import { type Ref } from 'vue'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))

View File

@ -11,7 +11,7 @@
"files": [
{
"name": "Calendar.vue",
"content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport type { Calendar } from 'v-calendar'\nimport { DatePicker } from 'v-calendar'\nimport { ChevronLeft, ChevronRight } from 'lucide-vue-next'\nimport { computed, nextTick, onMounted, ref } from 'vue'\nimport { buttonVariants } from '@/lib/registry/default/ui/button'\nimport { cn } from '@/lib/utils'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<{\n modelValue?: string | number | Date | Partial<{\n start: Date\n end: Date\n }>\n modelModifiers?: object\n columns?: number\n type?: 'single' | 'range'\n}>(), {\n type: 'single',\n columns: 1,\n})\nconst emits = defineEmits<{\n (e: 'update:modelValue', payload: typeof props.modelValue): void\n}>()\n\nconst modelValue = useVModel(props, 'modelValue', emits, {\n passive: true,\n})\n\nconst datePicker = ref<InstanceType<typeof DatePicker>>()\n// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.\nconst calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)\n\nfunction handleNav(direction: 'prev' | 'next') {\n if (!calendarRef.value)\n return\n\n if (direction === 'prev')\n calendarRef.value.movePrev()\n else calendarRef.value.moveNext()\n}\n\nonMounted(async () => {\n await nextTick()\n if (modelValue.value instanceof Date && calendarRef.value)\n calendarRef.value.focusDate(modelValue.value)\n})\n</script>\n\n<template>\n <div class=\"relative\">\n <div class=\"absolute top-3 flex justify-between w-full px-4\">\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('prev')\">\n <ChevronLeft class=\"w-4 h-4\" />\n </button>\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('next')\">\n <ChevronRight class=\"w-4 h-4\" />\n </button>\n </div>\n\n <DatePicker\n ref=\"datePicker\"\n v-model=\"modelValue\"\n v-bind=\"$attrs\"\n :model-modifiers=\"modelModifiers\"\n class=\"calendar\"\n trim-weeks\n :transition=\"'none'\"\n :columns=\"columns\"\n />\n </div>\n</template>\n\n<style lang=\"postcss\">\n.calendar {\n @apply p-3 text-center;\n}\n.calendar .vc-pane-layout {\n @apply grid gap-4;\n}\n.calendar .vc-title {\n @apply text-sm font-medium pointer-events-none;\n}\n.calendar .vc-pane-header-wrapper {\n @apply hidden;\n}\n.calendar .vc-weeks {\n @apply mt-4;\n}\n.calendar .vc-weekdays {\n @apply flex;\n}\n.calendar .vc-weekday {\n @apply text-muted-foreground rounded-md w-9 font-normal text-[0.8rem];\n}\n.calendar .vc-weeks {\n @apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;\n}\n.calendar .vc-day:has(.vc-highlights) {\n @apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden;\n}\n.calendar .vc-day-content {\n @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;\n}\n.calendar .vc-day-content:not(.vc-highlight-content-light) {\n @apply rounded-md;\n}\n.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),\n.calendar .vc-disabled {\n @apply text-muted-foreground opacity-50;\n}\n.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {\n @apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;\n}\n.calendar .vc-highlight-content-light {\n @apply bg-accent text-accent-foreground;\n}\n</style>\n"
"content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport type { Calendar } from 'v-calendar'\nimport { DatePicker } from 'v-calendar'\nimport { ChevronLeft, ChevronRight } from 'lucide-vue-next'\nimport { computed, nextTick, onMounted, ref } from 'vue'\nimport { buttonVariants } from '@/lib/registry/default/ui/button'\nimport { cn } from '@/lib/utils'\n\n/* Extracted from v-calendar */\ntype DatePickerModel = DatePickerDate | DatePickerRangeObject\ntype DateSource = Date | string | number\ntype DatePickerDate = DateSource | Partial<SimpleDateParts> | null\ninterface DatePickerRangeObject {\n start: Exclude<DatePickerDate, null>\n end: Exclude<DatePickerDate, null>\n}\ninterface SimpleDateParts {\n year: number\n month: number\n day: number\n hours: number\n minutes: number\n seconds: number\n milliseconds: number\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<{\n modelValue?: string | number | Date | DatePickerModel\n modelModifiers?: object\n columns?: number\n type?: 'single' | 'range'\n}>(), {\n type: 'single',\n columns: 1,\n})\nconst emits = defineEmits<{\n (e: 'update:modelValue', payload: typeof props.modelValue): void\n}>()\n\nconst modelValue = useVModel(props, 'modelValue', emits, {\n passive: true,\n})\n\nconst datePicker = ref<InstanceType<typeof DatePicker>>()\n// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.\nconst calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)\n\nfunction handleNav(direction: 'prev' | 'next') {\n if (!calendarRef.value)\n return\n\n if (direction === 'prev')\n calendarRef.value.movePrev()\n else calendarRef.value.moveNext()\n}\n\nonMounted(async () => {\n await nextTick()\n if (modelValue.value instanceof Date && calendarRef.value)\n calendarRef.value.focusDate(modelValue.value)\n})\n</script>\n\n<template>\n <div class=\"relative\">\n <div class=\"absolute top-3 flex justify-between w-full px-4\">\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('prev')\">\n <ChevronLeft class=\"w-4 h-4\" />\n </button>\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('next')\">\n <ChevronRight class=\"w-4 h-4\" />\n </button>\n </div>\n\n <DatePicker\n ref=\"datePicker\"\n v-model=\"modelValue\"\n v-bind=\"$attrs\"\n :model-modifiers=\"modelModifiers\"\n class=\"calendar\"\n trim-weeks\n :transition=\"'none'\"\n :columns=\"columns\"\n />\n </div>\n</template>\n\n<style lang=\"postcss\">\n.calendar {\n @apply p-3 text-center;\n}\n.calendar .vc-pane-layout {\n @apply grid gap-4;\n}\n.calendar .vc-title {\n @apply text-sm font-medium pointer-events-none;\n}\n.calendar .vc-pane-header-wrapper {\n @apply hidden;\n}\n.calendar .vc-weeks {\n @apply mt-4;\n}\n.calendar .vc-weekdays {\n @apply flex;\n}\n.calendar .vc-weekday {\n @apply text-muted-foreground rounded-md w-9 font-normal text-[0.8rem];\n}\n.calendar .vc-weeks {\n @apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;\n}\n.calendar .vc-day:has(.vc-highlights) {\n @apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden;\n}\n.calendar .vc-day-content {\n @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;\n}\n.calendar .vc-day-content:not(.vc-highlight-content-light) {\n @apply rounded-md;\n}\n.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),\n.calendar .vc-disabled {\n @apply text-muted-foreground opacity-50;\n}\n.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {\n @apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;\n}\n.calendar .vc-highlight-content-light {\n @apply bg-accent text-accent-foreground;\n}\n</style>\n"
},
{
"name": "index.ts",

View File

@ -9,7 +9,7 @@
"files": [
{
"name": "Checkbox.vue",
"content": "<script setup lang=\"ts\">\nimport type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'\nimport { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'\nimport { Check } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<CheckboxRootProps>()\nconst emits = defineEmits<CheckboxRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <CheckboxRoot\n v-bind=\"forwarded\"\n :class=\"\n cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n $attrs.class ?? '')\"\n >\n <CheckboxIndicator class=\"flex items-center justify-center text-current\">\n <Check class=\"h-4 w-4\" />\n </CheckboxIndicator>\n </CheckboxRoot>\n</template>\n"
"content": "<script setup lang=\"ts\">\nimport type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'\nimport { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'\nimport { Check } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<CheckboxRootProps>()\nconst emits = defineEmits<CheckboxRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <CheckboxRoot\n v-bind=\"forwarded\"\n :class=\"\n cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n $attrs.class ?? '')\"\n >\n <CheckboxIndicator class=\"flex h-full w-full items-center justify-center text-current\">\n <Check class=\"h-4 w-4\" />\n </CheckboxIndicator>\n </CheckboxRoot>\n</template>\n"
},
{
"name": "index.ts",

View File

@ -11,7 +11,7 @@
"files": [
{
"name": "Calendar.vue",
"content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport type { Calendar } from 'v-calendar'\nimport { DatePicker } from 'v-calendar'\nimport { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'\nimport { computed, nextTick, onMounted, ref } from 'vue'\nimport { buttonVariants } from '@/lib/registry/new-york/ui/button'\nimport { cn } from '@/lib/utils'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps< {\n modelValue?: string | number | Date | Partial<{\n start: Date\n end: Date\n }>\n modelModifiers?: object\n columns?: number\n type?: 'single' | 'range'\n}>(), {\n type: 'single',\n columns: 1,\n})\nconst emits = defineEmits<{\n (e: 'update:modelValue', payload: typeof props.modelValue): void\n}>()\n\nconst modelValue = useVModel(props, 'modelValue', emits, {\n passive: true,\n})\n\nconst datePicker = ref<InstanceType<typeof DatePicker>>()\n// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.\nconst calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)\n\nfunction handleNav(direction: 'prev' | 'next') {\n if (!calendarRef.value)\n return\n\n if (direction === 'prev')\n calendarRef.value.movePrev()\n else calendarRef.value.moveNext()\n}\n\nonMounted(async () => {\n await nextTick()\n if (modelValue.value instanceof Date && calendarRef.value)\n calendarRef.value.focusDate(modelValue.value)\n})\n</script>\n\n<template>\n <div class=\"relative\">\n <div class=\"absolute top-3 flex justify-between w-full px-4\">\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('prev')\">\n <ChevronLeftIcon class=\"w-4 h-4\" />\n </button>\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('next')\">\n <ChevronRightIcon class=\"w-4 h-4\" />\n </button>\n </div>\n\n <DatePicker\n ref=\"datePicker\"\n v-bind=\"$attrs\"\n v-model=\"modelValue\"\n :model-modifiers=\"modelModifiers\"\n class=\"calendar\"\n trim-weeks\n :transition=\"'none'\"\n :columns=\"columns\"\n />\n </div>\n</template>\n\n<style lang=\"postcss\">\n.calendar {\n @apply p-3 text-center;\n}\n.calendar .vc-pane-layout {\n @apply grid gap-4;\n}\n.calendar .vc-title {\n @apply text-sm font-medium pointer-events-none;\n}\n.calendar .vc-pane-header-wrapper {\n @apply hidden;\n}\n.calendar .vc-weeks {\n @apply mt-4;\n}\n.calendar .vc-weekdays {\n @apply flex;\n}\n.calendar .vc-weekday {\n @apply text-muted-foreground rounded-md w-8 font-normal text-[0.8rem];\n}\n.calendar .vc-weeks {\n @apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;\n}\n.calendar .vc-day:has(.vc-highlights) {\n @apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden;\n}\n.calendar .vc-day-content {\n @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;\n}\n.calendar .vc-day-content:not(.vc-highlight-content-light) {\n @apply rounded-md;\n}\n.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),\n.calendar .vc-disabled {\n @apply text-muted-foreground opacity-50;\n}\n.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {\n @apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;\n}\n.calendar .vc-highlight-content-light {\n @apply bg-accent text-accent-foreground;\n}\n</style>\n"
"content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport type { Calendar } from 'v-calendar'\nimport { DatePicker } from 'v-calendar'\nimport { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'\nimport { computed, nextTick, onMounted, ref } from 'vue'\nimport { buttonVariants } from '@/lib/registry/new-york/ui/button'\nimport { cn } from '@/lib/utils'\n\n/* Extracted from v-calendar */\ntype DatePickerModel = DatePickerDate | DatePickerRangeObject\ntype DateSource = Date | string | number\ntype DatePickerDate = DateSource | Partial<SimpleDateParts> | null\ninterface DatePickerRangeObject {\n start: Exclude<DatePickerDate, null>\n end: Exclude<DatePickerDate, null>\n}\ninterface SimpleDateParts {\n year: number\n month: number\n day: number\n hours: number\n minutes: number\n seconds: number\n milliseconds: number\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\nconst props = withDefaults(defineProps< {\n modelValue?: string | number | Date | DatePickerModel\n modelModifiers?: object\n columns?: number\n type?: 'single' | 'range'\n}>(), {\n type: 'single',\n columns: 1,\n})\nconst emits = defineEmits<{\n (e: 'update:modelValue', payload: typeof props.modelValue): void\n}>()\n\nconst modelValue = useVModel(props, 'modelValue', emits, {\n passive: true,\n})\n\nconst datePicker = ref<InstanceType<typeof DatePicker>>()\n// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.\nconst calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)\n\nfunction handleNav(direction: 'prev' | 'next') {\n if (!calendarRef.value)\n return\n\n if (direction === 'prev')\n calendarRef.value.movePrev()\n else calendarRef.value.moveNext()\n}\n\nonMounted(async () => {\n await nextTick()\n if (modelValue.value instanceof Date && calendarRef.value)\n calendarRef.value.focusDate(modelValue.value)\n})\n</script>\n\n<template>\n <div class=\"relative\">\n <div class=\"absolute top-3 flex justify-between w-full px-4\">\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('prev')\">\n <ChevronLeftIcon class=\"w-4 h-4\" />\n </button>\n <button :class=\"cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')\" @click=\"handleNav('next')\">\n <ChevronRightIcon class=\"w-4 h-4\" />\n </button>\n </div>\n\n <DatePicker\n ref=\"datePicker\"\n v-bind=\"$attrs\"\n v-model=\"modelValue\"\n :model-modifiers=\"modelModifiers\"\n class=\"calendar\"\n trim-weeks\n :transition=\"'none'\"\n :columns=\"columns\"\n />\n </div>\n</template>\n\n<style lang=\"postcss\">\n.calendar {\n @apply p-3 text-center;\n}\n.calendar .vc-pane-layout {\n @apply grid gap-4;\n}\n.calendar .vc-title {\n @apply text-sm font-medium pointer-events-none;\n}\n.calendar .vc-pane-header-wrapper {\n @apply hidden;\n}\n.calendar .vc-weeks {\n @apply mt-4;\n}\n.calendar .vc-weekdays {\n @apply flex;\n}\n.calendar .vc-weekday {\n @apply text-muted-foreground rounded-md w-8 font-normal text-[0.8rem];\n}\n.calendar .vc-weeks {\n @apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;\n}\n.calendar .vc-day:has(.vc-highlights) {\n @apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden;\n}\n.calendar .vc-day-content {\n @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;\n}\n.calendar .vc-day-content:not(.vc-highlight-content-light) {\n @apply rounded-md;\n}\n.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),\n.calendar .vc-disabled {\n @apply text-muted-foreground opacity-50;\n}\n.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {\n @apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;\n}\n.calendar .vc-highlight-content-light {\n @apply bg-accent text-accent-foreground;\n}\n</style>\n"
},
{
"name": "index.ts",

View File

@ -9,7 +9,7 @@
"files": [
{
"name": "Checkbox.vue",
"content": "<script setup lang=\"ts\">\nimport type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'\nimport { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'\nimport { CheckIcon } from '@radix-icons/vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<CheckboxRootProps>()\nconst emits = defineEmits<CheckboxRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <CheckboxRoot\n v-bind=\"forwarded\"\n :class=\"\n cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n $attrs.class ?? '')\"\n >\n <CheckboxIndicator class=\"flex items-center justify-center text-current\">\n <CheckIcon class=\"h-4 w-4\" />\n </CheckboxIndicator>\n </CheckboxRoot>\n</template>\n"
"content": "<script setup lang=\"ts\">\nimport type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'\nimport { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'\nimport { CheckIcon } from '@radix-icons/vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<CheckboxRootProps>()\nconst emits = defineEmits<CheckboxRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <CheckboxRoot\n v-bind=\"forwarded\"\n :class=\"\n cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n $attrs.class ?? '')\"\n >\n <CheckboxIndicator class=\"flex h-full w-full items-center justify-center text-current\">\n <CheckIcon class=\"h-4 w-4\" />\n </CheckboxIndicator>\n </CheckboxRoot>\n</template>\n"
},
{
"name": "index.ts",

View File

@ -34,7 +34,7 @@
},
{
"name": "CommandList.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxContentEmits, ComboboxContentProps } from 'radix-vue'\nimport { ComboboxContent, useForwardPropsEmits } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ComboboxContentProps>()\nconst emits = defineEmits<ComboboxContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <ComboboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', $attrs.class ?? '')\">\n <slot />\n </ComboboxContent>\n</template>\n"
"content": "<script setup lang=\"ts\">\nimport type { ComboboxContentEmits, ComboboxContentProps } from 'radix-vue'\nimport { ComboboxContent, useForwardPropsEmits } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ComboboxContentProps>()\nconst emits = defineEmits<ComboboxContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <ComboboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', $attrs.class ?? '')\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ComboboxContent>\n</template>\n"
},
{
"name": "CommandSeparator.vue",

View File

@ -1,23 +1,17 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"target": "esnext",
"lib": ["esnext", "dom"],
"jsx": "preserve",
"module": "esnext",
"moduleResolution": "node",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"moduleResolution": "Node",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["unplugin-icons/types/vue", "node"],
"resolveJsonModule": true,
"declaration": false,
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
"outDir": "dist"
},
"include": ["/**/*.vue", "src", ".vitepress/**/*.vue", "/**/*.ts", ".vitepress/**/*.mts", ".vitepress/**/*.vue", "src/lib/**/*"],
"include": ["src", ".vitepress/**/*.vue", ".vitepress/**/*.mts", ".vitepress/**/*.vue", "src/lib/**/*"],
"exclude": ["node_modules", "./scripts/build-registry.ts"]
}

View File

@ -0,0 +1,13 @@
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"moduleResolution": "Node",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"declaration": false
},
"include": ["src/lib/**/*"],
"exclude": ["node_modules", "src/lib/registry/**/example/**/*"]
}

View File

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

View File

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

View File

@ -128,10 +128,20 @@ module.exports = {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
"collapsible-down": {
from: { height: 0 },
to: { height: 'var(--radix-collapsible-content-height)' },
},
"collapsible-up": {
from: { height: 'var(--radix-collapsible-content-height)' },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"collapsible-down": "collapsible-down 0.2s ease-in-out",
"collapsible-up": "collapsible-up 0.2s ease-in-out",
},
},
},

View File

@ -1,7 +1,7 @@
import type * as z from 'zod'
import MagicString from 'magic-string'
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
import { parse, walk } from '@vue/compiler-sfc'
import { parse } from '@vue/compiler-sfc'
import { SyntaxKind } from 'ts-morph'
import type { registryBaseColorSchema } from '@/src/utils/registry/schema'
import type { Transformer } from '@/src/utils/transformers'

View File

@ -1,25 +1,25 @@
{
"compilerOptions": {
"incremental": true,
"target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"checkJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"jsx": "preserve",
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"noUncheckedIndexedAccess": true,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
}
},
"resolveJsonModule": true,
"allowJs": true,
"checkJs": true,
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true
},
"include": [
".eslintrc.cjs",

View File

@ -1,6 +1,5 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))

View File

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

View File

@ -124,10 +124,20 @@ export default {
from: { height: \\"var(--radix-accordion-content-height)\\" },
to: { height: 0 },
},
\\"collapsible-down\\": {
from: { height: 0 },
to: { height: 'var(--radix-collapsible-content-height)' },
},
\\"collapsible-up\\": {
from: { height: 'var(--radix-collapsible-content-height)' },
to: { height: 0 },
},
},
animation: {
\\"accordion-down\\": \\"accordion-down 0.2s ease-out\\",
\\"accordion-up\\": \\"accordion-up 0.2s ease-out\\",
\\"collapsible-down\\": \\"collapsible-down 0.2s ease-in-out\\",
\\"collapsible-up\\": \\"collapsible-up 0.2s ease-in-out\\",
},
},
},

View File

@ -77,7 +77,8 @@ describe('transformSFC', () => {
`,
config: {},
})
expect(result).toMatchSnapshot()
// TODO: Ignore test until https://github.com/radix-vue/shadcn-vue/issues/187 is resolved
// expect(result).toMatchSnapshot()
})
test('defineEmits', async () => {

View File

@ -2,12 +2,12 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.json",
"compilerOptions": {
"isolatedModules": false,
"baseUrl": ".",
"module": "ES2020",
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
},
"isolatedModules": false
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]

56
packages/module/.gitignore vendored Normal file
View File

@ -0,0 +1,56 @@
# Dependencies
node_modules
# Logs
*.log*
# Temp directories
.temp
.tmp
.cache
# Yarn
**/.yarn/cache
**/.yarn/*state*
# Generated dirs
dist
# Nuxt
.nuxt
.output
.data
.vercel_build_output
.build-*
.netlify
# Env
.env
# Testing
reports
coverage
*.lcov
.nyc_output
# VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Intellij idea
*.iml
.idea
# OSX
.DS_Store
.AppleDouble
.LSOverride
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

1
packages/module/.npmrc Normal file
View File

@ -0,0 +1 @@
shamefully-hoist=true

104
packages/module/README.md Normal file
View File

@ -0,0 +1,104 @@
<!--
Get your module up and running quickly.
Find and replace all on all files (CMD+SHIFT+F):
- Name: Shadcn Nuxt
- Package name: shadcn-nuxt
- Description: My new Nuxt module
-->
# Shadcn Nuxt
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
Shadcn Vue module for Nuxt.
- [✨ &nbsp;Release Notes](/CHANGELOG.md)
<!-- - [🏀 Online playground](https://stackblitz.com/github/radix-vue/shadcn-vue?file=playground%2Fapp.vue) -->
- [📖 &nbsp;Documentation](https://www.shadcn-vue.com/docs/installation/nuxt.html)
## Features
<!-- Highlight some of the features your module provide here -->
- ⛰ Auto-import correct and relevant components
- more to come...
## Quick Setup
1. Add `shadcn-nuxt` dependency to your project
```bash
# Using pnpm
pnpm add -D shadcn-nuxt
# Using yarn
yarn add --dev shadcn-nuxt
# Using npm
npm install --save-dev shadcn-nuxt
```
2. Add `shadcn-nuxt` to the `modules` section of `nuxt.config.ts`
```js
export default defineNuxtConfig({
modules: [
'shadcn-nuxt'
],
shadcn: {
/**
* Prefix for all the imported component
*/
prefix: '',
/**
* Directory that the component lives in.
* @default "./components/ui"
*/
componentDir: './components/ui'
}
})
```
That's it! You can now use Shadcn Nuxt in your Nuxt app ✨
## Development
```bash
# Install dependencies
npm install
# Generate type stubs
npm run dev:prepare
# Develop with the playground
npm run dev
# Build the playground
npm run dev:build
# Run ESLint
npm run lint
# Run Vitest
npm run test
npm run test:watch
# Release new version
npm run release
```
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/shadcn-nuxt/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-version-href]: https://npmjs.com/package/shadcn-nuxt
[npm-downloads-src]: https://img.shields.io/npm/dm/shadcn-nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-downloads-href]: https://npmjs.com/package/shadcn-nuxt
[license-src]: https://img.shields.io/npm/l/shadcn-nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D
[license-href]: https://npmjs.com/package/shadcn-nuxt
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
[nuxt-href]: https://nuxt.com

View File

@ -0,0 +1,51 @@
{
"name": "shadcn-nuxt",
"type": "module",
"version": "0.8.4",
"description": "Add shadcn-vue module to Nuxt",
"publishConfig": {
"access": "public"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/radix-vue/shadcn-vue.git",
"directory": "packages/module"
},
"exports": {
".": {
"types": "./dist/types.d.ts",
"import": "./dist/module.mjs",
"require": "./dist/module.cjs"
}
},
"main": "./dist/module.cjs",
"types": "./dist/types.d.ts",
"files": [
"dist"
],
"scripts": {
"prepack": "nuxt-module-build build",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
"lint": "eslint .",
"test": "vitest run",
"test:watch": "vitest watch",
"release": "pnpm run prepack && pnpm publish && git push --follow-tags"
},
"dependencies": {
"@nuxt/kit": "^3.8.2",
"ts-morph": "^19.0.0"
},
"devDependencies": {
"@nuxt/devtools": "latest",
"@nuxt/eslint-config": "^0.2.0",
"@nuxt/module-builder": "^0.5.4",
"@nuxt/schema": "^3.8.2",
"@nuxt/test-utils": "^3.8.1",
"@types/node": "^20.9.3",
"nuxt": "^3.8.2",
"vitest": "^0.33.0"
}
}

View File

@ -0,0 +1,11 @@
<script setup>
</script>
<template>
<div>
<UiButton :variant="'destructive'">
hi
</UiButton>
Nuxt module playground!
</div>
</template>

View File

@ -0,0 +1,15 @@
{
"style": "default",
"typescript": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "assets/css/tailwind.css",
"baseColor": "slate",
"cssVariables": true
},
"framework": "nuxt",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import { buttonVariants } from '.'
import { cn } from '@/lib/utils'
interface Props {
variant?: NonNullable<Parameters<typeof buttonVariants>[0]>['variant']
size?: NonNullable<Parameters<typeof buttonVariants>[0]>['size']
as?: string
}
withDefaults(defineProps<Props>(), {
as: 'button',
})
</script>
<template>
<component
:is="as"
:class="cn(buttonVariants({ variant, size }), $attrs.class ?? '')"
>
<slot />
</component>
</template>

View File

@ -0,0 +1,32 @@
import { cva } from 'class-variance-authority'
export { default as Button } from './Button.vue'
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',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<DropdownMenuRootProps>()
const emits = defineEmits<DropdownMenuRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<DropdownMenuRoot v-bind="forwarded">
<slot />
</DropdownMenuRoot>
</template>

View File

@ -0,0 +1,31 @@
<script setup lang="ts">
import {
DropdownMenuCheckboxItem,
type DropdownMenuCheckboxItemEmits,
type DropdownMenuCheckboxItemProps,
DropdownMenuItemIndicator,
useEmitAsProps,
} from 'radix-vue'
import { Check } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: string }>()
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
</script>
<template>
<DropdownMenuCheckboxItem
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class=" cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class,
)"
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<Check class="w-4 h-4" />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuCheckboxItem>
</template>

View File

@ -0,0 +1,4 @@
export { DropdownMenuPortal } from 'radix-vue'
export { default as DropdownMenu } from './DropdownMenu.vue'
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue'

View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -0,0 +1,7 @@
export default defineNuxtConfig({
modules: ['../src/module'],
shadcn: {
prefix: 'Ui',
},
devtools: { enabled: true },
})

View File

@ -0,0 +1,22 @@
{
"name": "my-module-playground",
"type": "module",
"private": true,
"scripts": {
"dev": "nuxi dev",
"build": "nuxi build",
"generate": "nuxi generate"
},
"dependencies": {
"@nuxtjs/tailwindcss": "^6.10.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-vue-next": "^0.276.0",
"radix-vue": "^1.2.3",
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"nuxt": "latest"
}
}

View File

@ -0,0 +1,73 @@
const animate = require('tailwindcss-animate')
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
plugins: [animate],
}

View File

@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

View File

@ -0,0 +1,65 @@
import { readdirSync } from 'node:fs'
import { addComponent, createResolver, defineNuxtModule } from '@nuxt/kit'
import { Project } from 'ts-morph'
// Module options TypeScript interface definition
export interface ModuleOptions {
/**
* Prefix for all the imported component
*/
prefix?: string
/**
* Directory that the component lives in.
* @default "./components/ui"
*/
componentDir?: string
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'shadcn',
configKey: 'shadcn',
},
defaults: {
prefix: '',
componentDir: './components/ui',
},
async setup(options, nuxt) {
const IGNORE_DIR = '**/components/ui'
const COMPONENT_DIR_PATH = options.componentDir!
const ROOT_DIR_PATH = nuxt.options.rootDir
const { resolve } = createResolver(ROOT_DIR_PATH)
nuxt.options.ignore.push(IGNORE_DIR)
nuxt._ignore?.add(IGNORE_DIR)
nuxt._ignorePatterns?.push(IGNORE_DIR)
try {
readdirSync(resolve(COMPONENT_DIR_PATH))
.forEach(async (dir) => {
const filePath = resolve(COMPONENT_DIR_PATH, dir, 'index.ts')
const project = new Project()
project.addSourceFileAtPath(filePath)
const sourceFile = project.getSourceFileOrThrow(filePath)
const exportedDeclarations = sourceFile.getExportedDeclarations()
// Filter out non-component export
const exportedKeys = Array.from(exportedDeclarations.keys()).filter(key => /^[A-Z]/.test(key))
exportedKeys.forEach((key) => {
addComponent({
name: `${options.prefix}${key}`, // name of the component to be used in vue templates
export: key, // (optional) if the component is a named (rather than default) export
filePath: resolve(filePath),
})
})
})
}
catch (err) {
if (err instanceof Error)
console.warn(err.message)
}
},
})

View File

@ -0,0 +1,15 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils'
describe('ssr', async () => {
await setup({
rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)),
})
it('renders the index page', async () => {
// Get response to a server-rendered page with `$fetch`.
const html = await $fetch('/')
expect(html).toContain('<div>basic</div>')
})
})

View File

@ -0,0 +1,6 @@
<script setup>
</script>
<template>
<div>basic</div>
</template>

View File

@ -0,0 +1,7 @@
import MyModule from '../../../src/module'
export default defineNuxtConfig({
modules: [
MyModule,
],
})

View File

@ -0,0 +1,5 @@
{
"name": "basic",
"type": "module",
"private": true
}

View File

@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

File diff suppressed because it is too large Load Diff