Merge branch 'dev' into feat/splitter
This commit is contained in:
commit
707da510b3
|
|
@ -74,7 +74,7 @@ export default defineConfig({
|
|||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
tailwind(),
|
||||
tailwind() as any,
|
||||
autoprefixer(),
|
||||
],
|
||||
},
|
||||
|
|
|
|||
54
apps/www/.vitepress/theme/components/InlineThemePicker.vue
Normal file
54
apps/www/.vitepress/theme/components/InlineThemePicker.vue
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Color } from '../types/colors'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { colors } from '@/lib/registry'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
|
||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||
|
||||
defineProps<{
|
||||
allColors: Color[]
|
||||
}>()
|
||||
|
||||
const { theme, setTheme } = useConfigStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<TooltipProvider
|
||||
v-for="(color, index) in allColors.slice(0, 5)"
|
||||
:key="index"
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
:key="index"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-border text-xs"
|
||||
:class="
|
||||
color === theme
|
||||
? 'border-primary'
|
||||
: 'border-transparent'
|
||||
"
|
||||
@click="setTheme(color)"
|
||||
>
|
||||
<span
|
||||
class="flex h-6 w-6 items-center justify-center rounded-full"
|
||||
:style="{ backgroundColor: colors[color][6].rgb }"
|
||||
>
|
||||
<RadixIconsCheck
|
||||
v-if="color === theme"
|
||||
class="h-4 w-4 text-white"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
align="center"
|
||||
:side-offset="1"
|
||||
class="capitalize bg-zinc-900 text-zinc-50"
|
||||
>
|
||||
{{ allColors[index] }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</template>
|
||||
106
apps/www/.vitepress/theme/components/ThemeCustomizer.vue
Normal file
106
apps/www/.vitepress/theme/components/ThemeCustomizer.vue
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<script lang="ts" setup>
|
||||
import { useData } from 'vitepress'
|
||||
import type { Color } from '../types/colors'
|
||||
import { RADII, useConfigStore } from '@/stores/config'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||
import { colors } from '@/lib/registry'
|
||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||
import RadixIconsSun from '~icons/radix-icons/sun'
|
||||
import RadixIconsMoon from '~icons/radix-icons/moon'
|
||||
|
||||
defineProps<{
|
||||
allColors: Color[]
|
||||
}>()
|
||||
|
||||
const { theme, radius, setRadius, setTheme } = useConfigStore()
|
||||
const { isDark } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="grid space-y-1">
|
||||
<h1 class="text-md text-foreground font-semibold">
|
||||
Customize
|
||||
</h1>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Pick a style and color for your components.
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="color" class="text-xs"> Color </Label>
|
||||
<div class="grid grid-cols-3 gap-2 py-1.5">
|
||||
<Button
|
||||
v-for="(color, index) in allColors"
|
||||
:key="index"
|
||||
variant="outline"
|
||||
class="h-8 justify-start px-3"
|
||||
:class="
|
||||
color === theme
|
||||
? 'border-foreground border-2'
|
||||
: ''
|
||||
"
|
||||
@click="setTheme(color)"
|
||||
>
|
||||
<span
|
||||
class="h-5 w-5 rounded-full flex items-center justify-center"
|
||||
:style="{ backgroundColor: colors[color][7].rgb }"
|
||||
>
|
||||
<RadixIconsCheck
|
||||
v-if="color === theme"
|
||||
class="h-3 w-3 text-white"
|
||||
/>
|
||||
</span>
|
||||
<span class="ml-2 text-xs capitalize">
|
||||
{{ color }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="radius" class="text-xs"> Radius </Label>
|
||||
<div class="grid grid-cols-5 gap-2 py-1.5">
|
||||
<Button
|
||||
v-for="(r, index) in RADII"
|
||||
:key="index"
|
||||
variant="outline"
|
||||
class="h-8 justify-center px-3"
|
||||
:class="
|
||||
r === radius
|
||||
? 'border-foreground border-2'
|
||||
: ''
|
||||
"
|
||||
@click="setRadius(r)"
|
||||
>
|
||||
<span class="text-xs">
|
||||
{{ r }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="theme" class="text-xs"> Theme </Label>
|
||||
|
||||
<div class="flex space-x-2 py-1.5">
|
||||
<Button
|
||||
class="h-8"
|
||||
variant="outline"
|
||||
:class="{ 'border-2 border-foreground': !isDark }"
|
||||
@click="isDark = false"
|
||||
>
|
||||
<RadixIconsSun class="w-4 h-4 mr-2" />
|
||||
<span class="text-xs">Light</span>
|
||||
</Button>
|
||||
<Button
|
||||
class="h-8"
|
||||
variant="outline"
|
||||
:class="{ 'border-2 border-foreground': isDark }"
|
||||
@click="isDark = true"
|
||||
>
|
||||
<RadixIconsMoon class="w-4 h-4 mr-2" />
|
||||
<span class="text-xs">Dark</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -214,6 +214,12 @@ export const docsConfig: DocsConfig = {
|
|||
href: '/docs/components/dialog',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Drawer',
|
||||
href: '/docs/components/drawer',
|
||||
items: [],
|
||||
label: 'New',
|
||||
},
|
||||
{
|
||||
title: 'Dropdown Menu',
|
||||
href: '/docs/components/dropdown-menu',
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ watch(() => $route.path, (n) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen flex-col bg-background">
|
||||
<div vaul-drawer-wrapper class="flex min-h-screen flex-col bg-background">
|
||||
<header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
|
||||
<div
|
||||
class="container flex justify-between h-14 max-w-screen-2xl items-center"
|
||||
|
|
@ -290,9 +290,7 @@ watch(() => $route.path, (n) => {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
<DefaultToaster />
|
||||
<ClientOnly>
|
||||
<NewYorkSonner :theme="isDark ? 'dark' : 'light'" />
|
||||
</ClientOnly>
|
||||
<NewYorkSonner :theme="'system'" />
|
||||
<NewYorkToaster />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { Paintbrush } from 'lucide-vue-next'
|
||||
import { useData } from 'vitepress'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||
import CustomizerCode from '../components/CustomizerCode.vue'
|
||||
import { RADII, useConfigStore } from '@/stores/config'
|
||||
import { colors } from '@/lib/registry'
|
||||
import type { Color } from '../types/colors'
|
||||
import ThemeCustomizer from '../components/ThemeCustomizer.vue'
|
||||
import InlineThemePicker from '../components/InlineThemePicker.vue'
|
||||
import PageAction from '../components/PageAction.vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/lib/registry/new-york/ui/dialog'
|
||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||
import RadixIconsSun from '~icons/radix-icons/sun'
|
||||
import RadixIconsMoon from '~icons/radix-icons/moon'
|
||||
|
||||
type Color =
|
||||
| 'zinc'
|
||||
| 'slate'
|
||||
| 'stone'
|
||||
| 'gray'
|
||||
| 'neutral'
|
||||
| 'red'
|
||||
| 'rose'
|
||||
| 'orange'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'yellow'
|
||||
| 'violet'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
|
||||
|
||||
// Create an array of color values
|
||||
const allColors: Color[] = [
|
||||
|
|
@ -47,8 +31,7 @@ const allColors: Color[] = [
|
|||
'violet',
|
||||
]
|
||||
|
||||
const { theme, radius, setRadius, setTheme } = useConfigStore()
|
||||
const { isDark } = useData()
|
||||
const { theme, radius } = useConfigStore()
|
||||
|
||||
// Whenever the component is mounted, update the document class list
|
||||
onMounted(() => {
|
||||
|
|
@ -72,173 +55,63 @@ watch(radius, (radius) => {
|
|||
|
||||
<template>
|
||||
<div class="container relative">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<PageHeader class="page-header pb-8">
|
||||
<PageHeaderHeading class="hidden md:block">
|
||||
Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
</PageHeader>
|
||||
</div>
|
||||
<div class="px-4 pb-8 md:ml-auto md:pb-0">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="hidden md:flex">
|
||||
<div class="mr-4 hidden items-center space-x-1 lg:flex">
|
||||
<TooltipProvider
|
||||
v-for="(color, index) in allColors.slice(0, 5)"
|
||||
:key="index"
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
:key="index"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-border text-xs"
|
||||
:class="
|
||||
color === theme
|
||||
? 'border-primary'
|
||||
: 'border-transparent'
|
||||
"
|
||||
@click="setTheme(color)"
|
||||
>
|
||||
<span
|
||||
class="flex h-6 w-6 items-center justify-center rounded-full"
|
||||
:style="{ backgroundColor: colors[color][6].rgb }"
|
||||
>
|
||||
<RadixIconsCheck
|
||||
v-if="color === theme"
|
||||
class="h-4 w-4 text-white"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
align="center"
|
||||
:side-offset="1"
|
||||
class="capitalize bg-zinc-900 text-zinc-50"
|
||||
>
|
||||
{{ allColors[index] }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
Customize
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent :side-offset="8" align="end" class="w-96">
|
||||
<div class="p-4">
|
||||
<div class="grid space-y-1">
|
||||
<h1 class="text-md text-foreground font-semibold">
|
||||
Customize
|
||||
</h1>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Pick a style and color for your components.
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="color" class="text-xs"> Color </Label>
|
||||
<div class="grid grid-cols-3 gap-2 py-1.5">
|
||||
<Button
|
||||
v-for="(color, index) in allColors"
|
||||
:key="index"
|
||||
variant="outline"
|
||||
class="h-8 justify-start px-3"
|
||||
:class="
|
||||
color === theme
|
||||
? 'border-foreground border-2'
|
||||
: ''
|
||||
"
|
||||
@click="setTheme(color)"
|
||||
>
|
||||
<span
|
||||
class="h-5 w-5 rounded-full flex items-center justify-center"
|
||||
:style="{ backgroundColor: colors[color][7].rgb }"
|
||||
>
|
||||
<RadixIconsCheck
|
||||
v-if="color === theme"
|
||||
class="h-3 w-3 text-white"
|
||||
/>
|
||||
</span>
|
||||
<span class="ml-2 text-xs capitalize">
|
||||
{{ color }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="radius" class="text-xs"> Radius </Label>
|
||||
<div class="grid grid-cols-5 gap-2 py-1.5">
|
||||
<Button
|
||||
v-for="(r, index) in RADII"
|
||||
:key="index"
|
||||
variant="outline"
|
||||
class="h-8 justify-center px-3"
|
||||
:class="
|
||||
r === radius
|
||||
? 'border-foreground border-2'
|
||||
: ''
|
||||
"
|
||||
@click="setRadius(r)"
|
||||
>
|
||||
<span class="text-xs">
|
||||
{{ r }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="theme" class="text-xs"> Theme </Label>
|
||||
<PageHeader>
|
||||
<PageHeaderHeading class="hidden md:block">
|
||||
Add colors. Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading class="md:hidden">
|
||||
Make it yours
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
|
||||
<PageAction>
|
||||
<InlineThemePicker class="gap-x-1 me-4 hidden lg:flex" :all-colors="allColors" />
|
||||
|
||||
<Drawer>
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline" class="md:hidden h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
Customize
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent class="p-6 pt-0">
|
||||
<ThemeCustomizer :all-colors="allColors" />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="hidden md:flex h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
Customize
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent :side-offset="8" align="end" class="w-96">
|
||||
<ThemeCustomizer :all-colors="allColors" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger as-child>
|
||||
<Button class="h-9 ml-2 rounded-[0.5rem]">
|
||||
Copy code
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Theme</DialogTitle>
|
||||
<DialogDescription>
|
||||
Copy and paste the following code into your CSS file.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CustomizerCode />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</PageAction>
|
||||
</PageHeader>
|
||||
|
||||
<div class="flex space-x-2 py-1.5">
|
||||
<Button
|
||||
class="h-8"
|
||||
variant="outline"
|
||||
:class="{ 'border-2 border-foreground': !isDark }"
|
||||
@click="isDark = false"
|
||||
>
|
||||
<RadixIconsSun class="w-4 h-4 mr-2" />
|
||||
<span class="text-xs">Light</span>
|
||||
</Button>
|
||||
<Button
|
||||
class="h-8"
|
||||
variant="outline"
|
||||
:class="{ 'border-2 border-foreground': isDark }"
|
||||
@click="isDark = true"
|
||||
>
|
||||
<RadixIconsMoon class="w-4 h-4 mr-2" />
|
||||
<span class="text-xs">Dark</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Dialog>
|
||||
<DialogTrigger as-child>
|
||||
<Button class="h-9 ml-2 rounded-[0.5rem]">
|
||||
Copy code
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Theme</DialogTitle>
|
||||
<DialogDescription>
|
||||
Copy and paste the following code into your CSS file.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CustomizerCode />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<slot />
|
||||
</section>
|
||||
|
|
|
|||
13
apps/www/.vitepress/theme/types/colors.ts
Normal file
13
apps/www/.vitepress/theme/types/colors.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export type Color =
|
||||
| 'zinc'
|
||||
| 'slate'
|
||||
| 'stone'
|
||||
| 'gray'
|
||||
| 'neutral'
|
||||
| 'red'
|
||||
| 'rose'
|
||||
| 'orange'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'yellow'
|
||||
| 'violet'
|
||||
|
|
@ -4,7 +4,7 @@ import { dependencies as deps } from '../../../package.json'
|
|||
import { Index as demoIndex } from '../../../../www/__registry__'
|
||||
import tailwindConfigRaw from '../../../tailwind.config?raw'
|
||||
import cssRaw from '../../../../../packages/cli/test/fixtures/nuxt/assets/css/tailwind.css?raw'
|
||||
import { type Style } from '@/lib/registry/styles'
|
||||
import type { Style } from '@/lib/registry/styles'
|
||||
|
||||
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
|
||||
let files: Record<string, any> = {}
|
||||
|
|
@ -54,7 +54,7 @@ export default defineConfig({
|
|||
<title>Vite + Vue + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div vaul-drawer-wrapper id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -90,6 +90,9 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
[iconPackage]: 'latest',
|
||||
'shadcn-vue': 'latest',
|
||||
'typescript': 'latest',
|
||||
'vaul-vue': 'latest',
|
||||
'@unovis/vue': 'latest',
|
||||
'@unovis/ts': 'latest',
|
||||
}
|
||||
|
||||
const devDependencies = {
|
||||
|
|
@ -164,7 +167,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
isBinary: false,
|
||||
content: `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))
|
||||
|
|
|
|||
|
|
@ -297,6 +297,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/ComboboxPopover.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/ComboboxPopover.vue"],
|
||||
},
|
||||
"ComboboxResponsive": {
|
||||
name: "ComboboxResponsive",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","command","drawer","popover"],
|
||||
component: () => import("../src/lib/registry/default/example/ComboboxResponsive.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/ComboboxResponsive.vue"],
|
||||
},
|
||||
"CommandDemo": {
|
||||
name: "CommandDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -402,6 +409,27 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DialogScrollOverlayDemo.vue"],
|
||||
},
|
||||
"DrawerDemo": {
|
||||
name: "DrawerDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","drawer"],
|
||||
component: () => import("../src/lib/registry/default/example/DrawerDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DrawerDemo.vue"],
|
||||
},
|
||||
"DrawerDialog": {
|
||||
name: "DrawerDialog",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dialog","drawer","label","input"],
|
||||
component: () => import("../src/lib/registry/default/example/DrawerDialog.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DrawerDialog.vue"],
|
||||
},
|
||||
"DropdownMenuCheckboxes": {
|
||||
name: "DropdownMenuCheckboxes",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/default/example/DropdownMenuCheckboxes.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DropdownMenuCheckboxes.vue"],
|
||||
},
|
||||
"DropdownMenuDemo": {
|
||||
name: "DropdownMenuDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -409,6 +437,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/DropdownMenuDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DropdownMenuDemo.vue"],
|
||||
},
|
||||
"DropdownMenuRadioGroup": {
|
||||
name: "DropdownMenuRadioGroup",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/default/example/DropdownMenuRadioGroup.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DropdownMenuRadioGroup.vue"],
|
||||
},
|
||||
"HoverCardDemo": {
|
||||
name: "HoverCardDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -605,6 +640,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/SelectForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SelectForm.vue"],
|
||||
},
|
||||
"SelectScrollable": {
|
||||
name: "SelectScrollable",
|
||||
type: "components:example",
|
||||
registryDependencies: ["select"],
|
||||
component: () => import("../src/lib/registry/default/example/SelectScrollable.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SelectScrollable.vue"],
|
||||
},
|
||||
"SeparatorDemo": {
|
||||
name: "SeparatorDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -626,6 +668,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/SheetSideDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SheetSideDemo.vue"],
|
||||
},
|
||||
"SkeletonCard": {
|
||||
name: "SkeletonCard",
|
||||
type: "components:example",
|
||||
registryDependencies: ["skeleton"],
|
||||
component: () => import("../src/lib/registry/default/example/SkeletonCard.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SkeletonCard.vue"],
|
||||
},
|
||||
"SkeletonDemo": {
|
||||
name: "SkeletonDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -640,6 +689,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/SliderDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SliderDemo.vue"],
|
||||
},
|
||||
"SliderForm": {
|
||||
name: "SliderForm",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","form","slider","toast"],
|
||||
component: () => import("../src/lib/registry/default/example/SliderForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SliderForm.vue"],
|
||||
},
|
||||
"SonnerDemo": {
|
||||
name: "SonnerDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1271,6 +1327,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/ComboboxPopover.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/ComboboxPopover.vue"],
|
||||
},
|
||||
"ComboboxResponsive": {
|
||||
name: "ComboboxResponsive",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","command","drawer","popover"],
|
||||
component: () => import("../src/lib/registry/new-york/example/ComboboxResponsive.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/ComboboxResponsive.vue"],
|
||||
},
|
||||
"CommandDemo": {
|
||||
name: "CommandDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1376,6 +1439,27 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue"],
|
||||
},
|
||||
"DrawerDemo": {
|
||||
name: "DrawerDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","drawer"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DrawerDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DrawerDemo.vue"],
|
||||
},
|
||||
"DrawerDialog": {
|
||||
name: "DrawerDialog",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dialog","drawer","label","input"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DrawerDialog.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DrawerDialog.vue"],
|
||||
},
|
||||
"DropdownMenuCheckboxes": {
|
||||
name: "DropdownMenuCheckboxes",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DropdownMenuCheckboxes.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DropdownMenuCheckboxes.vue"],
|
||||
},
|
||||
"DropdownMenuDemo": {
|
||||
name: "DropdownMenuDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1383,6 +1467,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/DropdownMenuDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DropdownMenuDemo.vue"],
|
||||
},
|
||||
"DropdownMenuRadioGroup": {
|
||||
name: "DropdownMenuRadioGroup",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DropdownMenuRadioGroup.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DropdownMenuRadioGroup.vue"],
|
||||
},
|
||||
"HoverCardDemo": {
|
||||
name: "HoverCardDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1579,6 +1670,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/SelectForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SelectForm.vue"],
|
||||
},
|
||||
"SelectScrollable": {
|
||||
name: "SelectScrollable",
|
||||
type: "components:example",
|
||||
registryDependencies: ["select"],
|
||||
component: () => import("../src/lib/registry/new-york/example/SelectScrollable.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SelectScrollable.vue"],
|
||||
},
|
||||
"SeparatorDemo": {
|
||||
name: "SeparatorDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1600,6 +1698,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/SheetSideDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SheetSideDemo.vue"],
|
||||
},
|
||||
"SkeletonCard": {
|
||||
name: "SkeletonCard",
|
||||
type: "components:example",
|
||||
registryDependencies: ["skeleton"],
|
||||
component: () => import("../src/lib/registry/new-york/example/SkeletonCard.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SkeletonCard.vue"],
|
||||
},
|
||||
"SkeletonDemo": {
|
||||
name: "SkeletonDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1614,6 +1719,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/SliderDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SliderDemo.vue"],
|
||||
},
|
||||
"SliderForm": {
|
||||
name: "SliderForm",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","form","slider","toast"],
|
||||
component: () => import("../src/lib/registry/new-york/example/SliderForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SliderForm.vue"],
|
||||
},
|
||||
"SonnerDemo": {
|
||||
name: "SonnerDemo",
|
||||
type: "components:example",
|
||||
|
|
|
|||
|
|
@ -16,28 +16,28 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@morev/vue-transitions": "^2.3.6",
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@stackblitz/sdk": "^1.9.0",
|
||||
"@tanstack/vue-table": "^8.11.8",
|
||||
"@unovis/ts": "^1.3.3",
|
||||
"@unovis/vue": "^1.3.3",
|
||||
"@tanstack/vue-table": "^8.13.2",
|
||||
"@unovis/ts": "^1.3.5",
|
||||
"@unovis/vue": "^1.3.5",
|
||||
"@vee-validate/zod": "^4.12.5",
|
||||
"@vueuse/core": "^10.7.2",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"codesandbox": "^2.2.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"embla-carousel": "^8.0.0-rc22",
|
||||
"embla-carousel-autoplay": "^8.0.0-rc22",
|
||||
"embla-carousel-vue": "^8.0.0-rc22",
|
||||
"date-fns": "^3.3.1",
|
||||
"embla-carousel": "^8.0.0",
|
||||
"embla-carousel-autoplay": "^8.0.0",
|
||||
"embla-carousel-vue": "^8.0.0",
|
||||
"lucide-vue-next": "^0.276.0",
|
||||
"radix-vue": "^1.4.8",
|
||||
"radix-vue": "^1.4.9",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vaul-vue": "^0.1.0",
|
||||
"vee-validate": "4.12.5",
|
||||
"vue": "^3.4.15",
|
||||
"vue-sonner": "^1.0.3",
|
||||
"vue": "^3.4.21",
|
||||
"vue-sonner": "^1.1.1",
|
||||
"vue-wrap-balancer": "^1.1.3",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
|
|
@ -46,27 +46,27 @@
|
|||
"@iconify-json/tabler": "^1.1.89",
|
||||
"@iconify/json": "^2.2.108",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@shikijs/transformers": "^1.0.0-beta.3",
|
||||
"@types/lodash.template": "^4.5.2",
|
||||
"@types/node": "^20.8.10",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@shikijs/transformers": "^1.1.7",
|
||||
"@types/lodash.template": "^4.5.3",
|
||||
"@types/node": "^20.11.24",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/compiler-core": "^3.4.15",
|
||||
"@vue/compiler-dom": "^3.4.15",
|
||||
"@vue/compiler-core": "^3.4.21",
|
||||
"@vue/compiler-dom": "^3.4.21",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"lodash.template": "^4.5.0",
|
||||
"oxc-parser": "^0.2.0",
|
||||
"oxc-parser": "^0.7.0",
|
||||
"pathe": "^1.1.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"shiki": "^1.0.0-beta.3",
|
||||
"shiki": "^1.1.7",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tsx": "^4.7.0",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.3.3",
|
||||
"unplugin-icons": "^0.18.3",
|
||||
"vite": "^5.0.12",
|
||||
"vitepress": "^1.0.0-rc.41",
|
||||
"vue-tsc": "^1.8.27"
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"vite": "^5.1.4",
|
||||
"vitepress": "^1.0.0-rc.44",
|
||||
"vue-tsc": "^2.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ description: Autocomplete input and command palette with a list of suggestions.
|
|||
|
||||
</Callout>
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
The Combobox is built using a composition of the `<Popover />` and the `<Command />` components.
|
||||
|
|
@ -110,6 +109,12 @@ const value = ref({})
|
|||
|
||||
<ComponentPreview name="ComboboxDropdownMenu" />
|
||||
|
||||
### Responsive
|
||||
|
||||
You can create a responsive combobox by using the `<Popover />` on desktop and the `<Drawer />` components on mobile.
|
||||
|
||||
<ComponentPreview name="ComboboxResponsive" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="ComboboxForm" />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ description: Powerful table and datagrids built using TanStack Table.
|
|||
primitive: https://tanstack.com/table/v8/docs/guide/introduction
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="DataTableDemo" />
|
||||
|
||||
## Introduction
|
||||
|
|
@ -56,7 +55,6 @@ npm install @tanstack/vue-table
|
|||
|
||||
<ComponentPreview name="DataTableColumnPinningDemo" />
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
We are going to build a table to show recent payments. Here's what our data looks like:
|
||||
|
|
@ -219,7 +217,6 @@ const table = useVueTable({
|
|||
|
||||
</Callout>
|
||||
|
||||
|
||||
### Render the table
|
||||
|
||||
Finally, we'll render our table in our index component.
|
||||
|
|
@ -270,7 +267,6 @@ Let's format the amount cell to display the dollar amount. We'll also align the
|
|||
|
||||
Update the `header` and `cell` definitions for amount as follows:
|
||||
|
||||
|
||||
```ts:line-numbers title="components/payments/columns.ts" {5-17}
|
||||
import { h } from 'vue'
|
||||
|
||||
|
|
@ -345,7 +341,6 @@ function copy(id: string) {
|
|||
|
||||
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
||||
|
||||
|
||||
```ts:line-numbers showLineNumber{2,6-16}
|
||||
import { ColumnDef } from "@tanstack/vue-table"
|
||||
import DropdownAction from '@/components/DataTableDropDown.vue'
|
||||
|
|
@ -459,7 +454,6 @@ Let's make the email column sortable.
|
|||
```ts:line-numbers {5,6,12-17}
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
|
||||
|
||||
import type { Updater } from '@tanstack/vue-table'
|
||||
import { type Ref } from 'vue'
|
||||
|
|
@ -971,11 +965,9 @@ export const columns = [
|
|||
{
|
||||
accessorKey: "email",
|
||||
header: ({ column }) => (
|
||||
return h(DataTableColumnHeader, {
|
||||
props: {
|
||||
column: column,
|
||||
title: 'Email'
|
||||
}
|
||||
h(DataTableColumnHeader, {
|
||||
column: column,
|
||||
title: 'Email'
|
||||
})
|
||||
),
|
||||
},
|
||||
|
|
|
|||
63
apps/www/src/content/docs/components/drawer.md
Normal file
63
apps/www/src/content/docs/components/drawer.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: Drawer
|
||||
description: A drawer component for vue.
|
||||
source: apps/www/src/lib/registry/default/ui/drawer
|
||||
primitive: https://github.com/radix-vue/vaul-vue
|
||||
---
|
||||
|
||||
<ComponentPreview name="DrawerDemo" />
|
||||
|
||||
## About
|
||||
|
||||
Drawer is built on top of [Vaul Vue](https://github.com/radix-vue/vaul-vue).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add drawer
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```vue showLineNumbers
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/components/ui/drawer'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer>
|
||||
<DrawerTrigger>Open</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
|
||||
<DrawerDescription>This action cannot be undone.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Responsive Dialog
|
||||
|
||||
You can combine the `Dialog` and `Drawer` components to create a responsive dialog. This renders a `Dialog` component on desktop and a `Drawer` on mobile.
|
||||
|
||||
<ComponentPreview name="DrawerDialog" />
|
||||
|
|
@ -5,7 +5,6 @@ source: apps/www/src/lib/registry/default/ui/dropdown-menu
|
|||
primitive: https://www.radix-vue.com/components/dropdown-menu.html
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="DropdownMenuDemo" />
|
||||
|
||||
## Installation
|
||||
|
|
@ -41,3 +40,13 @@ import {
|
|||
</DropdownMenu>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Checkboxes
|
||||
|
||||
<ComponentPreview name="DropdownMenuCheckboxes" />
|
||||
|
||||
### Radio Group
|
||||
|
||||
<ComponentPreview name="DropdownMenuRadioGroup" />
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ See the following links for more examples on how to use the `vee-validate` featu
|
|||
- [Input](/docs/components/input#form)
|
||||
- [Radio Group](/docs/components/radio-group#form)
|
||||
- [Select](/docs/components/select#form)
|
||||
- [Slider](/docs/components/slider#form)
|
||||
- [Switch](/docs/components/switch#form)
|
||||
- [Textarea](/docs/components/textarea#form)
|
||||
- [Combobox](/docs/components/combobox#form)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Input
|
|||
description: Displays a form input field or a component that looks like an input field.
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="InputDemo" class="max-w-xs" />
|
||||
|
||||
## Installation
|
||||
|
|
@ -26,8 +25,6 @@ npx shadcn-vue@latest add input
|
|||
|
||||
</Steps>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
</TabPreview>
|
||||
|
||||
|
|
@ -43,6 +40,8 @@ import { Input } from '@/components/ui/input'
|
|||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Default
|
||||
|
||||
<ComponentPreview name="InputDemo" class="max-w-xs" />
|
||||
|
|
|
|||
|
|
@ -5,12 +5,10 @@ source: apps/www/src/lib/registry/default/ui/select
|
|||
primitive: https://www.radix-vue.com/components/select.html
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="SelectDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add select
|
||||
```
|
||||
|
|
@ -49,6 +47,10 @@ import {
|
|||
|
||||
## Examples
|
||||
|
||||
### Scrollable
|
||||
|
||||
<ComponentPreview name="SelectScrollable" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="SelectForm" />
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ npx shadcn-vue@latest add skeleton
|
|||
|
||||
### Copy and paste the following code into your project
|
||||
|
||||
|
||||
<<< @/lib/registry/default/ui/skeleton/Skeleton.vue
|
||||
|
||||
</Steps>
|
||||
|
|
@ -40,3 +39,9 @@ import { Skeleton } from '@/components/ui/skeleton'
|
|||
<Skeleton class="w-[100px] h-5 rounded-full" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Card
|
||||
|
||||
<ComponentPreview name="SkeletonCard" />
|
||||
|
|
|
|||
|
|
@ -26,3 +26,9 @@ import { Slider } from '@/components/ui/slider'
|
|||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="SliderForm" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts" setup>
|
||||
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/default/ui/command'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/default/ui/drawer'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
|
||||
|
||||
interface Status {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const statuses: Status[] = [
|
||||
{
|
||||
value: 'backlog',
|
||||
label: 'Backlog',
|
||||
},
|
||||
{
|
||||
value: 'todo',
|
||||
label: 'Todo',
|
||||
},
|
||||
{
|
||||
value: 'in progress',
|
||||
label: 'In Progress',
|
||||
},
|
||||
{
|
||||
value: 'done',
|
||||
label: 'Done',
|
||||
},
|
||||
{
|
||||
value: 'canceled',
|
||||
label: 'Canceled',
|
||||
},
|
||||
]
|
||||
|
||||
const [UseTemplate, StatusList] = createReusableTemplate()
|
||||
const isDesktop = useMediaQuery('(min-width: 768px)')
|
||||
|
||||
const isOpen = ref(false)
|
||||
const selectedStatus = ref<Status | null>(null)
|
||||
|
||||
function onStatusSelect(status: Status) {
|
||||
selectedStatus.value = status
|
||||
isOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UseTemplate>
|
||||
<Command>
|
||||
<CommandInput placeholder="Filter status..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
v-for="status of statuses"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
@select="onStatusSelect(status)"
|
||||
>
|
||||
{{ status.label }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</UseTemplate>
|
||||
|
||||
<Popover v-if="isDesktop" v-model:open="isOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[200px] p-0" align="start">
|
||||
<StatusList />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Drawer v-else :open="isOpen" @update:open="(newOpenValue) => isOpen = newOpenValue">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div class="mt-4 border-t">
|
||||
<StatusList />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
111
apps/www/src/lib/registry/default/example/DrawerDemo.vue
Normal file
111
apps/www/src/lib/registry/default/example/DrawerDemo.vue
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Minus, Plus } from 'lucide-vue-next'
|
||||
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/lib/registry/default/ui/drawer'
|
||||
|
||||
const goal = ref(350)
|
||||
|
||||
type Data = typeof data[number]
|
||||
const data = [
|
||||
{ goal: 400 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 278 },
|
||||
{ goal: 189 },
|
||||
{ goal: 239 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 278 },
|
||||
{ goal: 189 },
|
||||
{ goal: 349 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer>
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open Drawer
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div class="mx-auto w-full max-w-sm">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Move Goal</DrawerTitle>
|
||||
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div class="p-4 pb-0">
|
||||
<div class="flex items-center justify-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-8 w-8 shrink-0 rounded-full"
|
||||
:disabled="goal <= 200"
|
||||
@click="goal -= 10"
|
||||
>
|
||||
<Minus class="h-4 w-4" />
|
||||
<span class="sr-only">Decrease</span>
|
||||
</Button>
|
||||
<div class="flex-1 text-center">
|
||||
<div class="text-7xl font-bold tracking-tighter">
|
||||
{{ goal }}
|
||||
</div>
|
||||
<div class="text-[0.70rem] uppercase text-muted-foreground">
|
||||
Calories/day
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-8 w-8 shrink-0 rounded-full"
|
||||
:disabled="goal >= 400"
|
||||
@click="goal += 10"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
<span class="sr-only">Increase</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="my-3 px-3 h-[120px]">
|
||||
<VisXYContainer
|
||||
:data="data"
|
||||
class="h-[120px]"
|
||||
:style="{
|
||||
'opacity': 0.9,
|
||||
'--theme-primary': `hsl(var(--foreground))`,
|
||||
}"
|
||||
>
|
||||
<VisStackedBar
|
||||
:x="(d: Data, i :number) => i"
|
||||
:y="(d: Data) => d.goal"
|
||||
color="var(--theme-primary)"
|
||||
:bar-padding="0.1"
|
||||
:rounded-corners="0"
|
||||
/>
|
||||
</VisXYContainer>
|
||||
</div>
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose as-child>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
90
apps/www/src/lib/registry/default/example/DrawerDialog.vue
Normal file
90
apps/www/src/lib/registry/default/example/DrawerDialog.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/lib/registry/default/ui/dialog'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/lib/registry/default/ui/drawer'
|
||||
import { Label } from '@/lib/registry/default/ui/label'
|
||||
import { Input } from '@/lib/registry/default/ui/input'
|
||||
|
||||
// Reuse `form` section
|
||||
const [UseTemplate, GridForm] = createReusableTemplate()
|
||||
const isDesktop = useMediaQuery('(min-width: 768px)')
|
||||
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UseTemplate>
|
||||
<form class="grid items-start gap-4 px-4">
|
||||
<div class="grid gap-2">
|
||||
<Label html-for="email">Email</Label>
|
||||
<Input id="email" type="email" default-value="shadcn@example.com" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label html-for="username">Username</Label>
|
||||
<Input id="username" default-value="@shadcn" />
|
||||
</div>
|
||||
<Button type="submit">
|
||||
Save changes
|
||||
</Button>
|
||||
</form>
|
||||
</UseTemplate>
|
||||
|
||||
<Dialog v-if="isDesktop" v-model:open="isOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Edit Profile
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<GridForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Drawer v-else v-model:open="isOpen">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Edit Profile
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader class="text-left">
|
||||
<DrawerTitle>Edit profile</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<GridForm />
|
||||
<DrawerFooter class="pt-2">
|
||||
<DrawerClose as-child>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DropdownMenuCheckboxItemProps } from 'radix-vue'
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/default/ui/dropdown-menu'
|
||||
|
||||
type Checked = DropdownMenuCheckboxItemProps['checked']
|
||||
|
||||
const showStatusBar = ref<Checked>(true)
|
||||
const showActivityBar = ref<Checked>(false)
|
||||
const showPanel = ref<Checked>(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56">
|
||||
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showStatusBar"
|
||||
>
|
||||
Status Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showActivityBar"
|
||||
disabled
|
||||
>
|
||||
Activity Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showPanel"
|
||||
>
|
||||
Panel
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/default/ui/dropdown-menu'
|
||||
|
||||
const position = ref('bottom')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56">
|
||||
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup v-model="position">
|
||||
<DropdownMenuRadioItem value="top">
|
||||
Top
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="bottom">
|
||||
Bottom
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="right">
|
||||
Right
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
|
@ -49,5 +49,5 @@ const onSubmit = handleSubmit((values) => {
|
|||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -50,5 +50,5 @@ const onSubmit = handleSubmit((values) => {
|
|||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
|||
117
apps/www/src/lib/registry/default/example/SelectScrollable.vue
Normal file
117
apps/www/src/lib/registry/default/example/SelectScrollable.vue
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/lib/registry/default/ui/select'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select>
|
||||
<SelectTrigger class="w-[280px]">
|
||||
<SelectValue placeholder="Select a timezone" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>North America</SelectLabel>
|
||||
<SelectItem value="est">
|
||||
Eastern Standard Time (EST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cst">
|
||||
Central Standard Time (CST)
|
||||
</SelectItem>
|
||||
<SelectItem value="mst">
|
||||
Mountain Standard Time (MST)
|
||||
</SelectItem>
|
||||
<SelectItem value="pst">
|
||||
Pacific Standard Time (PST)
|
||||
</SelectItem>
|
||||
<SelectItem value="akst">
|
||||
Alaska Standard Time (AKST)
|
||||
</SelectItem>
|
||||
<SelectItem value="hst">
|
||||
Hawaii Standard Time (HST)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Europe & Africa</SelectLabel>
|
||||
<SelectItem value="gmt">
|
||||
Greenwich Mean Time (GMT)
|
||||
</SelectItem>
|
||||
<SelectItem value="cet">
|
||||
Central European Time (CET)
|
||||
</SelectItem>
|
||||
<SelectItem value="eet">
|
||||
Eastern European Time (EET)
|
||||
</SelectItem>
|
||||
<SelectItem value="west">
|
||||
Western European Summer Time (WEST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cat">
|
||||
Central Africa Time (CAT)
|
||||
</SelectItem>
|
||||
<SelectItem value="eat">
|
||||
East Africa Time (EAT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Asia</SelectLabel>
|
||||
<SelectItem value="msk">
|
||||
Moscow Time (MSK)
|
||||
</SelectItem>
|
||||
<SelectItem value="ist">
|
||||
India Standard Time (IST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cst_china">
|
||||
China Standard Time (CST)
|
||||
</SelectItem>
|
||||
<SelectItem value="jst">
|
||||
Japan Standard Time (JST)
|
||||
</SelectItem>
|
||||
<SelectItem value="kst">
|
||||
Korea Standard Time (KST)
|
||||
</SelectItem>
|
||||
<SelectItem value="ist_indonesia">
|
||||
Indonesia Central Standard Time (WITA)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Australia & Pacific</SelectLabel>
|
||||
<SelectItem value="awst">
|
||||
Australian Western Standard Time (AWST)
|
||||
</SelectItem>
|
||||
<SelectItem value="acst">
|
||||
Australian Central Standard Time (ACST)
|
||||
</SelectItem>
|
||||
<SelectItem value="aest">
|
||||
Australian Eastern Standard Time (AEST)
|
||||
</SelectItem>
|
||||
<SelectItem value="nzst">
|
||||
New Zealand Standard Time (NZST)
|
||||
</SelectItem>
|
||||
<SelectItem value="fjt">
|
||||
Fiji Time (FJT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>South America</SelectLabel>
|
||||
<SelectItem value="art">
|
||||
Argentina Time (ART)
|
||||
</SelectItem>
|
||||
<SelectItem value="bot">
|
||||
Bolivia Time (BOT)
|
||||
</SelectItem>
|
||||
<SelectItem value="brt">
|
||||
Brasilia Time (BRT)
|
||||
</SelectItem>
|
||||
<SelectItem value="clt">
|
||||
Chile Standard Time (CLT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
13
apps/www/src/lib/registry/default/example/SkeletonCard.vue
Normal file
13
apps/www/src/lib/registry/default/example/SkeletonCard.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts" setup>
|
||||
import { Skeleton } from '@/lib/registry/default/ui/skeleton'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col space-y-3">
|
||||
<Skeleton class="h-[125px] w-[250px] rounded-xl" />
|
||||
<div class="space-y-2">
|
||||
<Skeleton class="h-4 w-[250px]" />
|
||||
<Skeleton class="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
63
apps/www/src/lib/registry/default/example/SliderForm.vue
Normal file
63
apps/www/src/lib/registry/default/example/SliderForm.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/lib/registry/default/ui/form'
|
||||
import { Slider } from '@/lib/registry/default/ui/slider'
|
||||
import { toast } from '@/lib/registry/default/ui/toast'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
duration: z.array(
|
||||
z.number().min(0).max(60),
|
||||
),
|
||||
}))
|
||||
|
||||
const { handleSubmit } = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="duration">
|
||||
<FormItem>
|
||||
<FormLabel>Duration</FormLabel>
|
||||
<FormControl>
|
||||
<Slider
|
||||
v-bind="componentField"
|
||||
:default-value="[30]"
|
||||
:max="60"
|
||||
:min="5"
|
||||
:step="5"
|
||||
/>
|
||||
<FormDescription class="flex justify-between">
|
||||
<span>How many minutes are you available?</span>
|
||||
<span>{{ componentField.modelValue?.[0] ?? "30" }} min</span>
|
||||
</FormDescription>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</template>
|
||||
19
apps/www/src/lib/registry/default/ui/drawer/Drawer.vue
Normal file
19
apps/www/src/lib/registry/default/ui/drawer/Drawer.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'
|
||||
import { DrawerRoot } from 'vaul-vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = withDefaults(defineProps<DrawerRootProps>(), {
|
||||
shouldScaleBackground: true,
|
||||
})
|
||||
|
||||
const emits = defineEmits<DrawerRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</DrawerRoot>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts" setup>
|
||||
import { DrawerContent, DrawerPortal } from 'vaul-vue'
|
||||
import type { DialogContentEmits, DialogContentProps } from 'radix-vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import DrawerOverlay from './DrawerOverlay.vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent
|
||||
v-bind="forwarded" :class="cn(
|
||||
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
<slot />
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerDescriptionProps } from 'vaul-vue'
|
||||
import { DrawerDescription } from 'vaul-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerDescription v-bind="delegatedProps" :class="cn('text-sm text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</DrawerDescription>
|
||||
</template>
|
||||
14
apps/www/src/lib/registry/default/ui/drawer/DrawerFooter.vue
Normal file
14
apps/www/src/lib/registry/default/ui/drawer/DrawerFooter.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
14
apps/www/src/lib/registry/default/ui/drawer/DrawerHeader.vue
Normal file
14
apps/www/src/lib/registry/default/ui/drawer/DrawerHeader.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts" setup>
|
||||
import { DrawerOverlay } from 'vaul-vue'
|
||||
import type { DialogOverlayProps } from 'radix-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerOverlay v-bind="delegatedProps" :class="cn('fixed inset-0 z-50 bg-black/80', props.class)" />
|
||||
</template>
|
||||
20
apps/www/src/lib/registry/default/ui/drawer/DrawerTitle.vue
Normal file
20
apps/www/src/lib/registry/default/ui/drawer/DrawerTitle.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerTitleProps } from 'vaul-vue'
|
||||
import { DrawerTitle } from 'vaul-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerTitle v-bind="delegatedProps" :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
|
||||
<slot />
|
||||
</DrawerTitle>
|
||||
</template>
|
||||
8
apps/www/src/lib/registry/default/ui/drawer/index.ts
Normal file
8
apps/www/src/lib/registry/default/ui/drawer/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { DrawerPortal, DrawerTrigger, DrawerClose } from 'vaul-vue'
|
||||
export { default as Drawer } from './Drawer.vue'
|
||||
export { default as DrawerOverlay } from './DrawerOverlay.vue'
|
||||
export { default as DrawerContent } from './DrawerContent.vue'
|
||||
export { default as DrawerHeader } from './DrawerHeader.vue'
|
||||
export { default as DrawerFooter } from './DrawerFooter.vue'
|
||||
export { default as DrawerTitle } from './DrawerTitle.vue'
|
||||
export { default as DrawerDescription } from './DrawerDescription.vue'
|
||||
|
|
@ -7,15 +7,16 @@ const props = defineProps<ToasterProps>()
|
|||
<template>
|
||||
<Sonner
|
||||
class="toaster group"
|
||||
:class-names="{
|
||||
toast:
|
||||
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
}"
|
||||
v-bind="props"
|
||||
:toast-options="{
|
||||
classes: {
|
||||
toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts" setup>
|
||||
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/new-york/ui/command'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||
|
||||
interface Status {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const statuses: Status[] = [
|
||||
{
|
||||
value: 'backlog',
|
||||
label: 'Backlog',
|
||||
},
|
||||
{
|
||||
value: 'todo',
|
||||
label: 'Todo',
|
||||
},
|
||||
{
|
||||
value: 'in progress',
|
||||
label: 'In Progress',
|
||||
},
|
||||
{
|
||||
value: 'done',
|
||||
label: 'Done',
|
||||
},
|
||||
{
|
||||
value: 'canceled',
|
||||
label: 'Canceled',
|
||||
},
|
||||
]
|
||||
|
||||
const [UseTemplate, StatusList] = createReusableTemplate()
|
||||
const isDesktop = useMediaQuery('(min-width: 768px)')
|
||||
|
||||
const isOpen = ref(false)
|
||||
const selectedStatus = ref<Status | null>(null)
|
||||
|
||||
function onStatusSelect(status: Status) {
|
||||
selectedStatus.value = status
|
||||
isOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UseTemplate>
|
||||
<Command>
|
||||
<CommandInput placeholder="Filter status..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
v-for="status of statuses"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
@select="onStatusSelect(status)"
|
||||
>
|
||||
{{ status.label }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</UseTemplate>
|
||||
|
||||
<Popover v-if="isDesktop" v-model:open="isOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[200px] p-0" align="start">
|
||||
<StatusList />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Drawer v-else v-model:open="isOpen">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div class="mt-4 border-t">
|
||||
<StatusList />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
111
apps/www/src/lib/registry/new-york/example/DrawerDemo.vue
Normal file
111
apps/www/src/lib/registry/new-york/example/DrawerDemo.vue
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Minus, Plus } from 'lucide-vue-next'
|
||||
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/lib/registry/default/ui/drawer'
|
||||
|
||||
const goal = ref(350)
|
||||
|
||||
type Data = typeof data[number]
|
||||
const data = [
|
||||
{ goal: 400 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 278 },
|
||||
{ goal: 189 },
|
||||
{ goal: 239 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 278 },
|
||||
{ goal: 189 },
|
||||
{ goal: 349 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer>
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open Drawer
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div class="mx-auto w-full max-w-sm">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Move Goal</DrawerTitle>
|
||||
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div class="p-4 pb-0">
|
||||
<div class="flex items-center justify-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-8 w-8 shrink-0 rounded-full"
|
||||
:disabled="goal <= 200"
|
||||
@click="goal -= 10"
|
||||
>
|
||||
<Minus class="h-4 w-4" />
|
||||
<span class="sr-only">Decrease</span>
|
||||
</Button>
|
||||
<div class="flex-1 text-center">
|
||||
<div class="text-7xl font-bold tracking-tighter">
|
||||
{{ goal }}
|
||||
</div>
|
||||
<div class="text-[0.70rem] uppercase text-muted-foreground">
|
||||
Calories/day
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-8 w-8 shrink-0 rounded-full"
|
||||
:disabled="goal >= 400"
|
||||
@click="goal += 10"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
<span class="sr-only">Increase</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="my-3 px-3 h-[120px]">
|
||||
<VisXYContainer
|
||||
:data="data"
|
||||
class="h-[120px]"
|
||||
:style="{
|
||||
'opacity': 0.9,
|
||||
'--theme-primary': `hsl(var(--foreground))`,
|
||||
}"
|
||||
>
|
||||
<VisStackedBar
|
||||
:x="(d: Data, i :number) => i"
|
||||
:y="(d: Data) => d.goal"
|
||||
color="var(--theme-primary)"
|
||||
:bar-padding="0.1"
|
||||
:rounded-corners="0"
|
||||
/>
|
||||
</VisXYContainer>
|
||||
</div>
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose as-child>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
90
apps/www/src/lib/registry/new-york/example/DrawerDialog.vue
Normal file
90
apps/www/src/lib/registry/new-york/example/DrawerDialog.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/lib/registry/default/ui/dialog'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/lib/registry/default/ui/drawer'
|
||||
import { Label } from '@/lib/registry/default/ui/label'
|
||||
import { Input } from '@/lib/registry/default/ui/input'
|
||||
|
||||
// Reuse `form` section
|
||||
const [UseTemplate, GridForm] = createReusableTemplate()
|
||||
const isDesktop = useMediaQuery('(min-width: 768px)')
|
||||
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UseTemplate>
|
||||
<form class="grid items-start gap-4 px-4">
|
||||
<div class="grid gap-2">
|
||||
<Label html-for="email">Email</Label>
|
||||
<Input id="email" type="email" default-value="shadcn@example.com" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label html-for="username">Username</Label>
|
||||
<Input id="username" default-value="@shadcn" />
|
||||
</div>
|
||||
<Button type="submit">
|
||||
Save changes
|
||||
</Button>
|
||||
</form>
|
||||
</UseTemplate>
|
||||
|
||||
<Dialog v-if="isDesktop" v-model:open="isOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Edit Profile
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<GridForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Drawer v-else v-model:open="isOpen">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Edit Profile
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader class="text-left">
|
||||
<DrawerTitle>Edit profile</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<GridForm />
|
||||
<DrawerFooter class="pt-2">
|
||||
<DrawerClose as-child>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DropdownMenuCheckboxItemProps } from 'radix-vue'
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
|
||||
type Checked = DropdownMenuCheckboxItemProps['checked']
|
||||
|
||||
const showStatusBar = ref<Checked>(true)
|
||||
const showActivityBar = ref<Checked>(false)
|
||||
const showPanel = ref<Checked>(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56">
|
||||
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showStatusBar"
|
||||
>
|
||||
Status Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showActivityBar"
|
||||
disabled
|
||||
>
|
||||
Activity Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showPanel"
|
||||
>
|
||||
Panel
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
|
||||
const position = ref('bottom')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56">
|
||||
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup v-model="position">
|
||||
<DropdownMenuRadioItem value="top">
|
||||
Top
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="bottom">
|
||||
Bottom
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="right">
|
||||
Right
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
|
@ -49,5 +49,5 @@ const onSubmit = handleSubmit((values) => {
|
|||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -50,5 +50,5 @@ const onSubmit = handleSubmit((values) => {
|
|||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
|||
117
apps/www/src/lib/registry/new-york/example/SelectScrollable.vue
Normal file
117
apps/www/src/lib/registry/new-york/example/SelectScrollable.vue
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/lib/registry/new-york/ui/select'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select>
|
||||
<SelectTrigger class="w-[280px]">
|
||||
<SelectValue placeholder="Select a timezone" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>North America</SelectLabel>
|
||||
<SelectItem value="est">
|
||||
Eastern Standard Time (EST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cst">
|
||||
Central Standard Time (CST)
|
||||
</SelectItem>
|
||||
<SelectItem value="mst">
|
||||
Mountain Standard Time (MST)
|
||||
</SelectItem>
|
||||
<SelectItem value="pst">
|
||||
Pacific Standard Time (PST)
|
||||
</SelectItem>
|
||||
<SelectItem value="akst">
|
||||
Alaska Standard Time (AKST)
|
||||
</SelectItem>
|
||||
<SelectItem value="hst">
|
||||
Hawaii Standard Time (HST)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Europe & Africa</SelectLabel>
|
||||
<SelectItem value="gmt">
|
||||
Greenwich Mean Time (GMT)
|
||||
</SelectItem>
|
||||
<SelectItem value="cet">
|
||||
Central European Time (CET)
|
||||
</SelectItem>
|
||||
<SelectItem value="eet">
|
||||
Eastern European Time (EET)
|
||||
</SelectItem>
|
||||
<SelectItem value="west">
|
||||
Western European Summer Time (WEST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cat">
|
||||
Central Africa Time (CAT)
|
||||
</SelectItem>
|
||||
<SelectItem value="eat">
|
||||
East Africa Time (EAT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Asia</SelectLabel>
|
||||
<SelectItem value="msk">
|
||||
Moscow Time (MSK)
|
||||
</SelectItem>
|
||||
<SelectItem value="ist">
|
||||
India Standard Time (IST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cst_china">
|
||||
China Standard Time (CST)
|
||||
</SelectItem>
|
||||
<SelectItem value="jst">
|
||||
Japan Standard Time (JST)
|
||||
</SelectItem>
|
||||
<SelectItem value="kst">
|
||||
Korea Standard Time (KST)
|
||||
</SelectItem>
|
||||
<SelectItem value="ist_indonesia">
|
||||
Indonesia Central Standard Time (WITA)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Australia & Pacific</SelectLabel>
|
||||
<SelectItem value="awst">
|
||||
Australian Western Standard Time (AWST)
|
||||
</SelectItem>
|
||||
<SelectItem value="acst">
|
||||
Australian Central Standard Time (ACST)
|
||||
</SelectItem>
|
||||
<SelectItem value="aest">
|
||||
Australian Eastern Standard Time (AEST)
|
||||
</SelectItem>
|
||||
<SelectItem value="nzst">
|
||||
New Zealand Standard Time (NZST)
|
||||
</SelectItem>
|
||||
<SelectItem value="fjt">
|
||||
Fiji Time (FJT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>South America</SelectLabel>
|
||||
<SelectItem value="art">
|
||||
Argentina Time (ART)
|
||||
</SelectItem>
|
||||
<SelectItem value="bot">
|
||||
Bolivia Time (BOT)
|
||||
</SelectItem>
|
||||
<SelectItem value="brt">
|
||||
Brasilia Time (BRT)
|
||||
</SelectItem>
|
||||
<SelectItem value="clt">
|
||||
Chile Standard Time (CLT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
13
apps/www/src/lib/registry/new-york/example/SkeletonCard.vue
Normal file
13
apps/www/src/lib/registry/new-york/example/SkeletonCard.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts" setup>
|
||||
import { Skeleton } from '@/lib/registry/new-york/ui/skeleton'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col space-y-3">
|
||||
<Skeleton class="h-[125px] w-[250px] rounded-xl" />
|
||||
<div class="space-y-2">
|
||||
<Skeleton class="h-4 w-[250px]" />
|
||||
<Skeleton class="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
63
apps/www/src/lib/registry/new-york/example/SliderForm.vue
Normal file
63
apps/www/src/lib/registry/new-york/example/SliderForm.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/lib/registry/new-york/ui/form'
|
||||
import { Slider } from '@/lib/registry/new-york/ui/slider'
|
||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
duration: z.array(
|
||||
z.number().min(0).max(60),
|
||||
),
|
||||
}))
|
||||
|
||||
const { handleSubmit } = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="duration">
|
||||
<FormItem>
|
||||
<FormLabel>Duration</FormLabel>
|
||||
<FormControl>
|
||||
<Slider
|
||||
v-bind="componentField"
|
||||
:default-value="[30]"
|
||||
:max="60"
|
||||
:min="5"
|
||||
:step="5"
|
||||
/>
|
||||
<FormDescription class="flex justify-between">
|
||||
<span>How many minutes are you available?</span>
|
||||
<span>{{ componentField.modelValue?.[0] ?? "30" }} min</span>
|
||||
</FormDescription>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</template>
|
||||
19
apps/www/src/lib/registry/new-york/ui/drawer/Drawer.vue
Normal file
19
apps/www/src/lib/registry/new-york/ui/drawer/Drawer.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'
|
||||
import { DrawerRoot } from 'vaul-vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = withDefaults(defineProps<DrawerRootProps>(), {
|
||||
shouldScaleBackground: true,
|
||||
})
|
||||
|
||||
const emits = defineEmits<DrawerRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</DrawerRoot>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts" setup>
|
||||
import { DrawerContent, DrawerPortal } from 'vaul-vue'
|
||||
import type { DialogContentEmits, DialogContentProps } from 'radix-vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import DrawerOverlay from './DrawerOverlay.vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent
|
||||
v-bind="forwarded" :class="cn(
|
||||
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
<slot />
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerDescriptionProps } from 'vaul-vue'
|
||||
import { DrawerDescription } from 'vaul-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerDescription v-bind="delegatedProps" :class="cn('text-sm text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</DrawerDescription>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts" setup>
|
||||
import { DrawerOverlay } from 'vaul-vue'
|
||||
import type { DialogOverlayProps } from 'radix-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerOverlay v-bind="delegatedProps" :class="cn('fixed inset-0 z-50 bg-black/80', props.class)" />
|
||||
</template>
|
||||
20
apps/www/src/lib/registry/new-york/ui/drawer/DrawerTitle.vue
Normal file
20
apps/www/src/lib/registry/new-york/ui/drawer/DrawerTitle.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerTitleProps } from 'vaul-vue'
|
||||
import { DrawerTitle } from 'vaul-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerTitle v-bind="delegatedProps" :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
|
||||
<slot />
|
||||
</DrawerTitle>
|
||||
</template>
|
||||
8
apps/www/src/lib/registry/new-york/ui/drawer/index.ts
Normal file
8
apps/www/src/lib/registry/new-york/ui/drawer/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { DrawerPortal, DrawerTrigger, DrawerClose } from 'vaul-vue'
|
||||
export { default as Drawer } from './Drawer.vue'
|
||||
export { default as DrawerOverlay } from './DrawerOverlay.vue'
|
||||
export { default as DrawerContent } from './DrawerContent.vue'
|
||||
export { default as DrawerHeader } from './DrawerHeader.vue'
|
||||
export { default as DrawerFooter } from './DrawerFooter.vue'
|
||||
export { default as DrawerTitle } from './DrawerTitle.vue'
|
||||
export { default as DrawerDescription } from './DrawerDescription.vue'
|
||||
|
|
@ -7,15 +7,16 @@ const props = defineProps<ToasterProps>()
|
|||
<template>
|
||||
<Sonner
|
||||
class="toaster group"
|
||||
:class-names="{
|
||||
toast:
|
||||
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
}"
|
||||
v-bind="props"
|
||||
:toast-options="{
|
||||
classes: {
|
||||
toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -244,6 +244,24 @@
|
|||
],
|
||||
"type": "components:ui"
|
||||
},
|
||||
{
|
||||
"name": "drawer",
|
||||
"dependencies": [],
|
||||
"registryDependencies": [
|
||||
"utils"
|
||||
],
|
||||
"files": [
|
||||
"ui/drawer/Drawer.vue",
|
||||
"ui/drawer/DrawerContent.vue",
|
||||
"ui/drawer/DrawerDescription.vue",
|
||||
"ui/drawer/DrawerFooter.vue",
|
||||
"ui/drawer/DrawerHeader.vue",
|
||||
"ui/drawer/DrawerOverlay.vue",
|
||||
"ui/drawer/DrawerTitle.vue",
|
||||
"ui/drawer/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
},
|
||||
{
|
||||
"name": "dropdown-menu",
|
||||
"dependencies": [],
|
||||
|
|
|
|||
42
apps/www/src/public/registry/styles/default/drawer.json
Normal file
42
apps/www/src/public/registry/styles/default/drawer.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "drawer",
|
||||
"dependencies": [],
|
||||
"registryDependencies": [
|
||||
"utils"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Drawer.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'\nimport { DrawerRoot } from 'vaul-vue'\nimport { useForwardPropsEmits } from 'radix-vue'\n\nconst props = withDefaults(defineProps<DrawerRootProps>(), {\n shouldScaleBackground: true,\n})\n\nconst emits = defineEmits<DrawerRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DrawerRoot v-bind=\"forwarded\">\n <slot />\n </DrawerRoot>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerContent.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport { DrawerContent, DrawerPortal } from 'vaul-vue'\nimport type { DialogContentEmits, DialogContentProps } from 'radix-vue'\nimport { useForwardPropsEmits } from 'radix-vue'\nimport type { HtmlHTMLAttributes } from 'vue'\nimport DrawerOverlay from './DrawerOverlay.vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()\nconst emits = defineEmits<DialogContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DrawerPortal>\n <DrawerOverlay />\n <DrawerContent\n v-bind=\"forwarded\" :class=\"cn(\n 'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',\n props.class,\n )\"\n >\n <div class=\"mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted\" />\n <slot />\n </DrawerContent>\n </DrawerPortal>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerDescription.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { DrawerDescriptionProps } from 'vaul-vue'\nimport { DrawerDescription } from 'vaul-vue'\nimport { type HtmlHTMLAttributes, computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <DrawerDescription v-bind=\"delegatedProps\" :class=\"cn('text-sm text-muted-foreground', props.class)\">\n <slot />\n </DrawerDescription>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerFooter.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { HtmlHTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<{\n class?: HtmlHTMLAttributes['class']\n}>()\n</script>\n\n<template>\n <div :class=\"cn('mt-auto flex flex-col gap-2 p-4', props.class)\">\n <slot />\n </div>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerHeader.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { HtmlHTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<{\n class?: HtmlHTMLAttributes['class']\n}>()\n</script>\n\n<template>\n <div :class=\"cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)\">\n <slot />\n </div>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerOverlay.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport { DrawerOverlay } from 'vaul-vue'\nimport type { DialogOverlayProps } from 'radix-vue'\nimport { type HtmlHTMLAttributes, computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <DrawerOverlay v-bind=\"delegatedProps\" :class=\"cn('fixed inset-0 z-50 bg-black/80', props.class)\" />\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerTitle.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { DrawerTitleProps } from 'vaul-vue'\nimport { DrawerTitle } from 'vaul-vue'\nimport { type HtmlHTMLAttributes, computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <DrawerTitle v-bind=\"delegatedProps\" :class=\"cn('text-lg font-semibold leading-none tracking-tight', props.class)\">\n <slot />\n </DrawerTitle>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "index.ts",
|
||||
"content": "export { DrawerPortal, DrawerTrigger, DrawerClose } from 'vaul-vue'\nexport { default as Drawer } from './Drawer.vue'\nexport { default as DrawerOverlay } from './DrawerOverlay.vue'\nexport { default as DrawerContent } from './DrawerContent.vue'\nexport { default as DrawerHeader } from './DrawerHeader.vue'\nexport { default as DrawerFooter } from './DrawerFooter.vue'\nexport { default as DrawerTitle } from './DrawerTitle.vue'\nexport { default as DrawerDescription } from './DrawerDescription.vue'\n"
|
||||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
"files": [
|
||||
{
|
||||
"name": "Sonner.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport { Toaster as Sonner, type ToasterProps } from 'vue-sonner'\n\nconst props = defineProps<ToasterProps>()\n</script>\n\n<template>\n <Sonner\n class=\"toaster group\"\n :class-names=\"{\n toast:\n 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton:\n 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton:\n 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n }\"\n v-bind=\"props\"\n />\n</template>\n"
|
||||
"content": "<script lang=\"ts\" setup>\nimport { Toaster as Sonner, type ToasterProps } from 'vue-sonner'\n\nconst props = defineProps<ToasterProps>()\n</script>\n\n<template>\n <Sonner\n class=\"toaster group\"\n v-bind=\"props\"\n :toast-options=\"{\n classes: {\n toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton:\n 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton:\n 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n },\n }\"\n />\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "index.ts",
|
||||
|
|
|
|||
42
apps/www/src/public/registry/styles/new-york/drawer.json
Normal file
42
apps/www/src/public/registry/styles/new-york/drawer.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "drawer",
|
||||
"dependencies": [],
|
||||
"registryDependencies": [
|
||||
"utils"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Drawer.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'\nimport { DrawerRoot } from 'vaul-vue'\nimport { useForwardPropsEmits } from 'radix-vue'\n\nconst props = withDefaults(defineProps<DrawerRootProps>(), {\n shouldScaleBackground: true,\n})\n\nconst emits = defineEmits<DrawerRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DrawerRoot v-bind=\"forwarded\">\n <slot />\n </DrawerRoot>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerContent.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport { DrawerContent, DrawerPortal } from 'vaul-vue'\nimport type { DialogContentEmits, DialogContentProps } from 'radix-vue'\nimport { useForwardPropsEmits } from 'radix-vue'\nimport type { HtmlHTMLAttributes } from 'vue'\nimport DrawerOverlay from './DrawerOverlay.vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()\nconst emits = defineEmits<DialogContentEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DrawerPortal>\n <DrawerOverlay />\n <DrawerContent\n v-bind=\"forwarded\" :class=\"cn(\n 'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',\n props.class,\n )\"\n >\n <div class=\"mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted\" />\n <slot />\n </DrawerContent>\n </DrawerPortal>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerDescription.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { DrawerDescriptionProps } from 'vaul-vue'\nimport { DrawerDescription } from 'vaul-vue'\nimport { type HtmlHTMLAttributes, computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <DrawerDescription v-bind=\"delegatedProps\" :class=\"cn('text-sm text-muted-foreground', props.class)\">\n <slot />\n </DrawerDescription>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerFooter.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { HtmlHTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<{\n class?: HtmlHTMLAttributes['class']\n}>()\n</script>\n\n<template>\n <div :class=\"cn('mt-auto flex flex-col gap-2 p-4', props.class)\">\n <slot />\n </div>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerHeader.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { HtmlHTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<{\n class?: HtmlHTMLAttributes['class']\n}>()\n</script>\n\n<template>\n <div :class=\"cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)\">\n <slot />\n </div>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerOverlay.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport { DrawerOverlay } from 'vaul-vue'\nimport type { DialogOverlayProps } from 'radix-vue'\nimport { type HtmlHTMLAttributes, computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <DrawerOverlay v-bind=\"delegatedProps\" :class=\"cn('fixed inset-0 z-50 bg-black/80', props.class)\" />\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "DrawerTitle.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport type { DrawerTitleProps } from 'vaul-vue'\nimport { DrawerTitle } from 'vaul-vue'\nimport { type HtmlHTMLAttributes, computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <DrawerTitle v-bind=\"delegatedProps\" :class=\"cn('text-lg font-semibold leading-none tracking-tight', props.class)\">\n <slot />\n </DrawerTitle>\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "index.ts",
|
||||
"content": "export { DrawerPortal, DrawerTrigger, DrawerClose } from 'vaul-vue'\nexport { default as Drawer } from './Drawer.vue'\nexport { default as DrawerOverlay } from './DrawerOverlay.vue'\nexport { default as DrawerContent } from './DrawerContent.vue'\nexport { default as DrawerHeader } from './DrawerHeader.vue'\nexport { default as DrawerFooter } from './DrawerFooter.vue'\nexport { default as DrawerTitle } from './DrawerTitle.vue'\nexport { default as DrawerDescription } from './DrawerDescription.vue'\n"
|
||||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
"files": [
|
||||
{
|
||||
"name": "Sonner.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport { Toaster as Sonner, type ToasterProps } from 'vue-sonner'\n\nconst props = defineProps<ToasterProps>()\n</script>\n\n<template>\n <Sonner\n class=\"toaster group\"\n :class-names=\"{\n toast:\n 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton:\n 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton:\n 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n }\"\n v-bind=\"props\"\n />\n</template>\n"
|
||||
"content": "<script lang=\"ts\" setup>\nimport { Toaster as Sonner, type ToasterProps } from 'vue-sonner'\n\nconst props = defineProps<ToasterProps>()\n</script>\n\n<template>\n <Sonner\n class=\"toaster group\"\n v-bind=\"props\"\n :toast-options=\"{\n classes: {\n toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton:\n 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton:\n 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n },\n }\"\n />\n</template>\n"
|
||||
},
|
||||
{
|
||||
"name": "index.ts",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@
|
|||
},
|
||||
"components": {
|
||||
"type": "string"
|
||||
},
|
||||
"ui": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["utils", "components"]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default antfu(
|
|||
'no-tabs': 0,
|
||||
'import/first': 0,
|
||||
'node/prefer-global/process': 0,
|
||||
'style/no-tabs': 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -3,7 +3,7 @@
|
|||
"type": "module",
|
||||
"version": "0.9.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.15.3",
|
||||
"packageManager": "pnpm@8.15.4",
|
||||
"license": "MIT",
|
||||
"repository": "radix-vue/shadcn-vue",
|
||||
"workspaces": [
|
||||
|
|
@ -27,11 +27,11 @@
|
|||
"taze:minor": "taze minor -fwri --ignore-paths ./packages/cli/test/** --exclude /@iconify/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.6.4",
|
||||
"@commitlint/cli": "^18.6.1",
|
||||
"@commitlint/config-conventional": "^18.6.2",
|
||||
"bumpp": "^9.3.0",
|
||||
"eslint": "^8.56.0",
|
||||
"@antfu/eslint-config": "^2.7.0",
|
||||
"@commitlint/cli": "^19.0.3",
|
||||
"@commitlint/config-conventional": "^19.0.3",
|
||||
"bumpp": "^9.3.1",
|
||||
"eslint": "^8.57.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"taze": "^0.13.3",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"dev": "tsup --watch",
|
||||
"build": "tsup",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rimraf dist && rimraf components",
|
||||
"clean": "node ./scripts/rimraf.js",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"start:dev": "COMPONENTS_REGISTRY_URL=http://localhost:3001 node dist/index.js",
|
||||
|
|
@ -45,41 +45,39 @@
|
|||
"test:ui": "vitest --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antfu/ni": "^0.21.8",
|
||||
"@babel/core": "^7.22.17",
|
||||
"@babel/parser": "^7.22.16",
|
||||
"@babel/plugin-transform-typescript": "^7.22.15",
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/parser": "^7.24.0",
|
||||
"@vue/compiler-sfc": "^3.4",
|
||||
"chalk": "5.3.0",
|
||||
"commander": "^11.0.0",
|
||||
"cosmiconfig": "^8.3.6",
|
||||
"c12": "^1.9.0",
|
||||
"commander": "^12.0.0",
|
||||
"consola": "^3.2.3",
|
||||
"detype": "npm:detypes@^0.7.9",
|
||||
"diff": "^5.1.0",
|
||||
"execa": "^8.0.1",
|
||||
"fs-extra": "^11.1.1",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"diff": "^5.2.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"https-proxy-agent": "^7.0.4",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.30.3",
|
||||
"node-fetch": "^3.3.2",
|
||||
"ora": "^7.0.1",
|
||||
"magic-string": "^0.30.8",
|
||||
"nypm": "^0.3.8",
|
||||
"ofetch": "^1.3.3",
|
||||
"ora": "^8.0.1",
|
||||
"pathe": "^1.1.2",
|
||||
"prompts": "^2.4.2",
|
||||
"radix-vue": "^1.4.8",
|
||||
"recast": "^0.23.4",
|
||||
"rimraf": "^5.0.1",
|
||||
"ts-morph": "^19.0.0",
|
||||
"radix-vue": "^1.4.9",
|
||||
"ts-morph": "^21.0.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/lodash.template": "^4.5.1",
|
||||
"@types/prompts": "^2.4.4",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/diff": "^5.0.9",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash.template": "^4.5.3",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@vitest/ui": "^0.34.4",
|
||||
"tsup": "^7.2.0",
|
||||
"type-fest": "^4.3.1",
|
||||
"typescript": "^5.2.2"
|
||||
"tsup": "^8.0.2",
|
||||
"type-fest": "^4.10.3",
|
||||
"typescript": "^5.3.3",
|
||||
"vite-tsconfig-paths": "^4.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
packages/cli/scripts/rimraf.js
Normal file
10
packages/cli/scripts/rimraf.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import fsp from 'node:fs/promises'
|
||||
|
||||
function rmdir(dirs) {
|
||||
dirs.forEach(async (dir) => {
|
||||
await fsp.unlink(dir).catch(() => {})
|
||||
await fsp.rm(dir, { recursive: true, force: true }).catch(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
rmdir(['dist', 'components'])
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
import { existsSync, promises as fs, rmSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import chalk from 'chalk'
|
||||
import path from 'pathe'
|
||||
import { consola } from 'consola'
|
||||
import { colors } from 'consola/utils'
|
||||
import { Command } from 'commander'
|
||||
import { execa } from 'execa'
|
||||
import ora from 'ora'
|
||||
import prompts from 'prompts'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import { addDependency, addDevDependency } from 'nypm'
|
||||
import { transform } from '@/src/utils/transformers'
|
||||
import { getConfig } from '@/src/utils/get-config'
|
||||
import { getPackageManager } from '@/src/utils/get-package-manager'
|
||||
import { handleError } from '@/src/utils/handle-error'
|
||||
import { logger } from '@/src/utils/logger'
|
||||
import {
|
||||
fetchTree,
|
||||
getItemTargetPath,
|
||||
|
|
@ -52,15 +51,15 @@ export const add = new Command()
|
|||
const cwd = path.resolve(options.cwd)
|
||||
|
||||
if (!existsSync(cwd)) {
|
||||
logger.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
consola.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const config = await getConfig(cwd)
|
||||
|
||||
if (!config) {
|
||||
logger.warn(
|
||||
`Configuration is missing. Please run ${chalk.green('init')} to create a components.json file.`,
|
||||
)
|
||||
consola.warn(`Configuration is missing. Please run ${colors.green('init')} to create a components.json file.`)
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +87,7 @@ export const add = new Command()
|
|||
}
|
||||
|
||||
if (!selectedComponents?.length) {
|
||||
logger.warn('No components selected. Exiting.')
|
||||
consola.warn('No components selected. Exiting.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +96,7 @@ export const add = new Command()
|
|||
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
|
||||
|
||||
if (!payload.length) {
|
||||
logger.warn('Selected components not found. Exiting.')
|
||||
consola.warn('Selected components not found. Exiting.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +113,6 @@ export const add = new Command()
|
|||
}
|
||||
|
||||
const spinner = ora('Installing components...').start()
|
||||
const skippedDeps = new Set<string>()
|
||||
for (const item of payload) {
|
||||
spinner.text = `Installing ${item.name}...`
|
||||
const targetDir = getItemTargetPath(
|
||||
|
|
@ -144,8 +142,8 @@ export const add = new Command()
|
|||
})
|
||||
|
||||
if (!overwrite) {
|
||||
logger.info(
|
||||
`Skipped ${item.name}. To overwrite, run with the ${chalk.green(
|
||||
consola.info(
|
||||
`Skipped ${item.name}. To overwrite, run with the ${colors.green(
|
||||
'--overwrite',
|
||||
)} flag.`,
|
||||
)
|
||||
|
|
@ -159,6 +157,20 @@ export const add = new Command()
|
|||
}
|
||||
}
|
||||
|
||||
// Install dependencies.
|
||||
await Promise.allSettled(
|
||||
[
|
||||
item.dependencies?.length && await addDependency(item.dependencies, {
|
||||
cwd,
|
||||
silent: true,
|
||||
}),
|
||||
item.devDependencies?.length && await addDevDependency(item.devDependencies, {
|
||||
cwd,
|
||||
silent: true,
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
||||
const componentDir = path.resolve(targetDir, item.name)
|
||||
if (!existsSync(componentDir))
|
||||
await fs.mkdir(componentDir, { recursive: true })
|
||||
|
|
@ -201,25 +213,6 @@ export const add = new Command()
|
|||
|
||||
await fs.writeFile(filePath, content)
|
||||
}
|
||||
|
||||
// Install dependencies.
|
||||
if (item.dependencies?.length) {
|
||||
item.dependencies.forEach(dep =>
|
||||
skippedDeps.add(dep),
|
||||
)
|
||||
|
||||
const packageManager = await getPackageManager(cwd)
|
||||
await execa(
|
||||
packageManager,
|
||||
[
|
||||
packageManager === 'npm' ? 'install' : 'add',
|
||||
...item.dependencies,
|
||||
],
|
||||
{
|
||||
cwd,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
spinner.succeed('Done.')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { existsSync, promises as fs } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import chalk from 'chalk'
|
||||
import path from 'pathe'
|
||||
import { consola } from 'consola'
|
||||
import { colors } from 'consola/utils'
|
||||
import { Command } from 'commander'
|
||||
import { type Change, diffLines } from 'diff'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import type { Config } from '@/src/utils/get-config'
|
||||
import { getConfig } from '@/src/utils/get-config'
|
||||
import { handleError } from '@/src/utils/handle-error'
|
||||
import { logger } from '@/src/utils/logger'
|
||||
import {
|
||||
fetchTree,
|
||||
getItemTargetPath,
|
||||
|
|
@ -45,14 +45,14 @@ export const diff = new Command()
|
|||
const cwd = path.resolve(options.cwd)
|
||||
|
||||
if (!existsSync(cwd)) {
|
||||
logger.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
consola.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const config = await getConfig(cwd)
|
||||
if (!config) {
|
||||
logger.warn(
|
||||
`Configuration is missing. Please run ${chalk.green(
|
||||
consola.warn(
|
||||
`Configuration is missing. Please run ${colors.green(
|
||||
'init',
|
||||
)} to create a components.json file.`,
|
||||
)
|
||||
|
|
@ -88,19 +88,20 @@ export const diff = new Command()
|
|||
}
|
||||
|
||||
if (!componentsWithUpdates.length) {
|
||||
logger.info('No updates found.')
|
||||
consola.info('No updates found.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
logger.info('The following components have updates available:')
|
||||
consola.info('The following components have updates available:')
|
||||
for (const component of componentsWithUpdates) {
|
||||
logger.info(`- ${component.name}`)
|
||||
consola.info(`- ${component.name}`)
|
||||
for (const change of component.changes)
|
||||
logger.info(` - ${change.filePath}`)
|
||||
consola.info(` - ${change.filePath}`)
|
||||
}
|
||||
logger.break()
|
||||
logger.info(
|
||||
`Run ${chalk.green('diff <component>')} to see the changes.`,
|
||||
|
||||
consola.log('')
|
||||
consola.info(
|
||||
`Run ${colors.green('diff <component>')} to see the changes.`,
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
|
@ -111,8 +112,8 @@ export const diff = new Command()
|
|||
)
|
||||
|
||||
if (!component) {
|
||||
logger.error(
|
||||
`The component ${chalk.green(options.component)} does not exist.`,
|
||||
consola.error(
|
||||
`The component ${colors.green(options.component)} does not exist.`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
@ -120,14 +121,14 @@ export const diff = new Command()
|
|||
const changes = await diffComponent(component, config)
|
||||
|
||||
if (!changes.length) {
|
||||
logger.info(`No updates found for ${options.component}.`)
|
||||
consola.info(`No updates found for ${options.component}.`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
for (const change of changes) {
|
||||
logger.info(`- ${change.filePath}`)
|
||||
consola.info(`- ${change.filePath}`)
|
||||
printDiff(change.patch)
|
||||
logger.info('')
|
||||
consola.log('')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
|
@ -184,10 +185,10 @@ function printDiff(diff: Change[]) {
|
|||
diff.forEach((part) => {
|
||||
if (part) {
|
||||
if (part.added)
|
||||
return process.stdout.write(chalk.green(part.value))
|
||||
return process.stdout.write(colors.green(part.value))
|
||||
|
||||
if (part.removed)
|
||||
return process.stdout.write(chalk.red(part.value))
|
||||
return process.stdout.write(colors.red(part.value))
|
||||
|
||||
return process.stdout.write(part.value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
import { existsSync, promises as fs } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import chalk from 'chalk'
|
||||
import path from 'pathe'
|
||||
import { Command } from 'commander'
|
||||
import { execa } from 'execa'
|
||||
import template from 'lodash.template'
|
||||
import ora from 'ora'
|
||||
import prompts from 'prompts'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import { addDependency, addDevDependency } from 'nypm'
|
||||
import { consola } from 'consola'
|
||||
import { colors } from 'consola/utils'
|
||||
import * as templates from '../utils/templates'
|
||||
import {
|
||||
getRegistryBaseColor,
|
||||
getRegistryBaseColors,
|
||||
getRegistryStyles,
|
||||
} from '../utils/registry'
|
||||
import { logger } from '../utils/logger'
|
||||
import { handleError } from '../utils/handle-error'
|
||||
import { getPackageManager } from '../utils/get-package-manager'
|
||||
import { transformByDetype } from '../utils/transformers/transform-sfc'
|
||||
import {
|
||||
type Config,
|
||||
|
|
@ -29,6 +28,7 @@ import {
|
|||
resolveConfigPaths,
|
||||
} from '../utils/get-config'
|
||||
import { transformCJSToESM } from '../utils/transformers/transform-cjs-to-esm'
|
||||
import { applyPrefixesCss } from '../utils/transformers/transform-tw-prefix'
|
||||
|
||||
const PROJECT_DEPENDENCIES = {
|
||||
base: [
|
||||
|
|
@ -64,7 +64,7 @@ export const init = new Command()
|
|||
|
||||
// Ensure target directory exists.
|
||||
if (!existsSync(cwd)) {
|
||||
logger.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
consola.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -74,11 +74,11 @@ export const init = new Command()
|
|||
|
||||
await runInit(cwd, config)
|
||||
|
||||
logger.info('')
|
||||
logger.info(
|
||||
`${chalk.green('Success!')} Project initialization completed.`,
|
||||
consola.log('')
|
||||
consola.info(
|
||||
`${colors.green('Success!')} Project initialization completed.`,
|
||||
)
|
||||
logger.info('')
|
||||
consola.log('')
|
||||
}
|
||||
catch (error) {
|
||||
handleError(error)
|
||||
|
|
@ -90,7 +90,7 @@ export async function promptForConfig(
|
|||
defaultConfig: Config | null = null,
|
||||
skip = false,
|
||||
) {
|
||||
const highlight = (text: string) => chalk.cyan(text)
|
||||
const highlight = (text: string) => colors.cyan(text)
|
||||
|
||||
const styles = await getRegistryStyles()
|
||||
const baseColors = await getRegistryBaseColors()
|
||||
|
|
@ -151,6 +151,14 @@ export async function promptForConfig(
|
|||
active: 'yes',
|
||||
inactive: 'no',
|
||||
},
|
||||
// {
|
||||
// type: 'text',
|
||||
// name: 'tailwindPrefix',
|
||||
// message: `Are you using a custom ${highlight(
|
||||
// 'tailwind prefix eg. tw-',
|
||||
// )}? (Leave blank if not)`,
|
||||
// initial: '',
|
||||
// },
|
||||
{
|
||||
type: 'text',
|
||||
name: 'tailwindConfig',
|
||||
|
|
@ -187,6 +195,7 @@ export async function promptForConfig(
|
|||
css: options.tailwindCss,
|
||||
baseColor: options.tailwindBaseColor,
|
||||
cssVariables: options.tailwindCssVariables,
|
||||
// prefix: options.tailwindPrefix,
|
||||
},
|
||||
aliases: {
|
||||
utils: options.utils,
|
||||
|
|
@ -207,7 +216,7 @@ export async function promptForConfig(
|
|||
}
|
||||
|
||||
// Write to file.
|
||||
logger.info('')
|
||||
consola.log('')
|
||||
const spinner = ora('Writing components.json...').start()
|
||||
const targetPath = path.resolve(cwd, 'components.json')
|
||||
await fs.writeFile(targetPath, JSON.stringify(config, null, 2), 'utf8')
|
||||
|
|
@ -247,8 +256,8 @@ export async function runInit(cwd: string, config: Config) {
|
|||
transformCJSToESM(
|
||||
config.resolvedPaths.tailwindConfig,
|
||||
config.tailwind.cssVariables
|
||||
? template(templates.TAILWIND_CONFIG_WITH_VARIABLES)({ extension, framework: config.framework })
|
||||
: template(templates.TAILWIND_CONFIG)({ extension, framework: config.framework }),
|
||||
? template(templates.TAILWIND_CONFIG_WITH_VARIABLES)({ extension, framework: config.framework, prefix: config.tailwind.prefix })
|
||||
: template(templates.TAILWIND_CONFIG)({ extension, framework: config.framework, prefix: config.tailwind.prefix }),
|
||||
),
|
||||
'utf8',
|
||||
)
|
||||
|
|
@ -259,7 +268,9 @@ export async function runInit(cwd: string, config: Config) {
|
|||
await fs.writeFile(
|
||||
config.resolvedPaths.tailwindCss,
|
||||
config.tailwind.cssVariables
|
||||
? baseColor.cssVarsTemplate
|
||||
? config.tailwind.prefix
|
||||
? applyPrefixesCss(baseColor.cssVarsTemplate, config.tailwind.prefix)
|
||||
: baseColor.cssVarsTemplate
|
||||
: baseColor.inlineColorsTemplate,
|
||||
'utf8',
|
||||
)
|
||||
|
|
@ -276,20 +287,29 @@ export async function runInit(cwd: string, config: Config) {
|
|||
|
||||
// Install dependencies.
|
||||
const dependenciesSpinner = ora('Installing dependencies...')?.start()
|
||||
const packageManager = await getPackageManager(cwd)
|
||||
|
||||
const deps = PROJECT_DEPENDENCIES.base.concat(
|
||||
config.framework === 'nuxt' ? PROJECT_DEPENDENCIES.nuxt : [],
|
||||
).concat(
|
||||
config.style === 'new-york' ? ['@radix-icons/vue'] : ['lucide-vue-next'],
|
||||
).filter(Boolean)
|
||||
|
||||
await execa(
|
||||
packageManager,
|
||||
[packageManager === 'npm' ? 'install' : 'add', ...deps],
|
||||
{
|
||||
cwd,
|
||||
},
|
||||
async function addNuxtDevDeps() {
|
||||
if (config.framework === 'nuxt') {
|
||||
await addDevDependency(PROJECT_DEPENDENCIES.nuxt, {
|
||||
cwd,
|
||||
silent: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.allSettled(
|
||||
[
|
||||
addNuxtDevDeps(),
|
||||
addDependency(deps, {
|
||||
cwd,
|
||||
silent: true,
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
||||
dependenciesSpinner?.succeed()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import path from 'node:path'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { cosmiconfig } from 'cosmiconfig'
|
||||
import path from 'pathe'
|
||||
import { loadConfig as c12LoadConfig } from 'c12'
|
||||
import type { ConfigLoaderResult } from 'tsconfig-paths'
|
||||
import { loadConfig } from 'tsconfig-paths'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import { resolveImport } from '@/src/utils/resolve-import'
|
||||
|
||||
export const DEFAULT_STYLE = 'default'
|
||||
|
|
@ -19,12 +19,6 @@ export const TAILWIND_CSS_PATH = {
|
|||
astro: 'src/styles/globals.css',
|
||||
}
|
||||
|
||||
// TODO: Figure out if we want to support all cosmiconfig formats.
|
||||
// A simple components.json file would be nice.
|
||||
const explorer = cosmiconfig('components', {
|
||||
searchPlaces: ['components.json'],
|
||||
})
|
||||
|
||||
export const rawConfigSchema = z
|
||||
.object({
|
||||
$schema: z.string().optional(),
|
||||
|
|
@ -35,11 +29,13 @@ export const rawConfigSchema = z
|
|||
css: z.string(),
|
||||
baseColor: z.string(),
|
||||
cssVariables: z.boolean().default(true),
|
||||
prefix: z.string().optional(),
|
||||
}),
|
||||
framework: z.string().default('Vite'),
|
||||
aliases: z.object({
|
||||
components: z.string(),
|
||||
utils: z.string(),
|
||||
ui: z.string().default('').optional(),
|
||||
}),
|
||||
})
|
||||
.strict()
|
||||
|
|
@ -53,6 +49,7 @@ export const configSchema = rawConfigSchema
|
|||
tailwindCss: z.string(),
|
||||
utils: z.string(),
|
||||
components: z.string(),
|
||||
ui: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
|
|
@ -103,15 +100,22 @@ export async function resolveConfigPaths(cwd: string, config: RawConfig) {
|
|||
tailwindCss: path.resolve(cwd, config.tailwind.css),
|
||||
utils: resolveImport(config.aliases.utils, tsConfig),
|
||||
components: resolveImport(config.aliases.components, tsConfig),
|
||||
ui: config.aliases.ui
|
||||
? resolveImport(config.aliases.ui, tsConfig)
|
||||
: resolveImport(config.aliases.components, tsConfig),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
|
||||
try {
|
||||
const configResult = await explorer.search(cwd)
|
||||
const configResult = await c12LoadConfig({
|
||||
name: 'components',
|
||||
configFile: 'components',
|
||||
cwd,
|
||||
})
|
||||
|
||||
if (!configResult)
|
||||
if (!configResult.config || Object.keys(configResult.config).length === 0)
|
||||
return null
|
||||
|
||||
return rawConfigSchema.parse(configResult.config)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'pathe'
|
||||
import fs from 'fs-extra'
|
||||
import { type PackageJson } from 'type-fest'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
import { detect } from '@antfu/ni'
|
||||
|
||||
export async function getPackageManager(
|
||||
targetDir: string,
|
||||
): Promise<'yarn' | 'pnpm' | 'bun' | 'npm'> {
|
||||
const packageManager = await detect({ programmatic: true, cwd: targetDir })
|
||||
|
||||
if (packageManager === 'yarn@berry')
|
||||
return 'yarn'
|
||||
if (packageManager === 'pnpm@6')
|
||||
return 'pnpm'
|
||||
if (packageManager === 'bun')
|
||||
return 'bun'
|
||||
|
||||
return packageManager ?? 'npm'
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { existsSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import path from 'pathe'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
export async function getProjectInfo() {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { logger } from './logger'
|
||||
import { consola } from 'consola'
|
||||
|
||||
export function handleError(error: unknown) {
|
||||
if (typeof error === 'string') {
|
||||
logger.error(error)
|
||||
consola.error(error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message)
|
||||
consola.error(error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
logger.error('Something went wrong. Please try again.')
|
||||
consola.error('Something went wrong. Please try again.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
import chalk from 'chalk'
|
||||
|
||||
export const logger = {
|
||||
error(...args: unknown[]) {
|
||||
console.log(chalk.red(...args))
|
||||
},
|
||||
warn(...args: unknown[]) {
|
||||
console.log(chalk.yellow(...args))
|
||||
},
|
||||
info(...args: unknown[]) {
|
||||
console.log(chalk.cyan(...args))
|
||||
},
|
||||
success(...args: unknown[]) {
|
||||
console.log(chalk.green(...args))
|
||||
},
|
||||
break() {
|
||||
console.log('')
|
||||
},
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import path from 'pathe'
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent'
|
||||
import fetch from 'node-fetch'
|
||||
import { ofetch } from 'ofetch'
|
||||
import type * as z from 'zod'
|
||||
import consola from 'consola'
|
||||
import {
|
||||
registryBaseColorSchema,
|
||||
registryIndexSchema,
|
||||
|
|
@ -122,9 +123,12 @@ export function getItemTargetPath(
|
|||
override?: string,
|
||||
) {
|
||||
// Allow overrides for all items but ui.
|
||||
if (override && item.type !== 'components:ui')
|
||||
if (override)
|
||||
return override
|
||||
|
||||
if (item.type === 'components:ui' && config.aliases.ui)
|
||||
return config.resolvedPaths.ui
|
||||
|
||||
const [parent, type] = item.type.split(':')
|
||||
if (!(parent in config.resolvedPaths))
|
||||
return null
|
||||
|
|
@ -139,17 +143,18 @@ async function fetchRegistry(paths: string[]) {
|
|||
try {
|
||||
const results = await Promise.all(
|
||||
paths.map(async (path) => {
|
||||
const response = await fetch(`${baseUrl}/registry/${path}`, {
|
||||
const response = await ofetch(`${baseUrl}/registry/${path}`, {
|
||||
// @ts-expect-error agent type
|
||||
agent,
|
||||
})
|
||||
return await response.json()
|
||||
|
||||
return response
|
||||
}),
|
||||
)
|
||||
return results
|
||||
}
|
||||
catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
consola.error(error)
|
||||
throw new Error(`Failed to fetch registry from ${baseUrl}.`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
|
||||
// TODO: Extract this to a shared package.
|
||||
export const registryItemSchema = z.object({
|
||||
name: z.string(),
|
||||
dependencies: z.array(z.string()).optional(),
|
||||
devDependencies: z.array(z.string()).optional(),
|
||||
registryDependencies: z.array(z.string()).optional(),
|
||||
files: z.array(z.string()),
|
||||
type: z.enum(['components:ui', 'components:component', 'components:example']),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export const UTILS = `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))
|
||||
|
|
@ -18,6 +17,7 @@ module.exports = {
|
|||
'./app/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
],
|
||||
prefix: "<%- prefix %>",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
|
|
@ -52,6 +52,7 @@ export const TAILWIND_CONFIG_WITH_VARIABLES = `const animate = require("tailwind
|
|||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
safelist: ["dark"],
|
||||
prefix: "<%- prefix %>",
|
||||
<% if (framework === 'vite') { %>
|
||||
content: [
|
||||
'./pages/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { promises as fs } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import path from 'pathe'
|
||||
import { Project, ScriptKind, type SourceFile } from 'ts-morph'
|
||||
import type * as z from 'zod'
|
||||
import type { Config } from '@/src/utils/get-config'
|
||||
import type { registryBaseColorSchema } from '@/src/utils/registry/schema'
|
||||
import { transformCssVars } from '@/src/utils/transformers/transform-css-vars'
|
||||
import { transformImport } from '@/src/utils/transformers/transform-import'
|
||||
import { transformTwPrefixes } from '@/src/utils/transformers/transform-tw-prefix'
|
||||
import { transformSFC } from '@/src/utils/transformers/transform-sfc'
|
||||
|
||||
export interface TransformOpts {
|
||||
|
|
@ -25,6 +26,7 @@ export type Transformer<Output = SourceFile> = (
|
|||
const transformers: Transformer[] = [
|
||||
transformCssVars,
|
||||
transformImport,
|
||||
// transformTwPrefixes,
|
||||
]
|
||||
|
||||
const project = new Project({
|
||||
|
|
|
|||
|
|
@ -88,29 +88,28 @@ export function applyColorMapping(
|
|||
if (input.includes(' border '))
|
||||
input = input.replace(' border ', ' border border-border ')
|
||||
|
||||
// Build color mappings.
|
||||
const classNames = input.split(' ')
|
||||
const lightMode: string[] = []
|
||||
const darkMode: string[] = []
|
||||
const lightMode = new Set<string>()
|
||||
const darkMode = new Set<string>()
|
||||
for (const className of classNames) {
|
||||
const [variant, value, modifier] = splitClassName(className)
|
||||
const prefix = PREFIXES.find(prefix => value?.startsWith(prefix))
|
||||
if (!prefix) {
|
||||
if (!lightMode.includes(className))
|
||||
lightMode.push(className)
|
||||
if (!lightMode.has(className))
|
||||
lightMode.add(className)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const needle = value?.replace(prefix, '')
|
||||
if (needle && needle in mapping.light) {
|
||||
lightMode.push(
|
||||
lightMode.add(
|
||||
[variant, `${prefix}${mapping.light[needle]}`]
|
||||
.filter(Boolean)
|
||||
.join(':') + (modifier ? `/${modifier}` : ''),
|
||||
)
|
||||
|
||||
darkMode.push(
|
||||
darkMode.add(
|
||||
['dark', variant, `${prefix}${mapping.dark[needle]}`]
|
||||
.filter(Boolean)
|
||||
.join(':') + (modifier ? `/${modifier}` : ''),
|
||||
|
|
@ -118,9 +117,9 @@ export function applyColorMapping(
|
|||
continue
|
||||
}
|
||||
|
||||
if (!lightMode.includes(className))
|
||||
lightMode.push(className)
|
||||
if (!lightMode.has(className))
|
||||
lightMode.add(className)
|
||||
}
|
||||
const combined = `${lightMode.join(' ').replace(/\'/g, '')} ${darkMode.join(' ').trim()}`.trim()
|
||||
return `${combined}`
|
||||
|
||||
return [...Array.from(lightMode), ...Array.from(darkMode)].join(' ').trim()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,19 @@ export const transformImport: Transformer = async ({ sourceFile, config }) => {
|
|||
|
||||
// Replace @/lib/registry/[style] with the components alias.
|
||||
if (moduleSpecifier.startsWith('@/lib/registry/')) {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
moduleSpecifier.replace(
|
||||
/^@\/lib\/registry\/[^/]+/,
|
||||
config.aliases.components,
|
||||
),
|
||||
)
|
||||
if (config.aliases.ui) {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
moduleSpecifier.replace(/^@\/lib\/registry\/[^/]+\/ui/, config.aliases.ui),
|
||||
)
|
||||
}
|
||||
else {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
moduleSpecifier.replace(
|
||||
/^@\/lib\/registry\/[^/]+/,
|
||||
config.aliases.components,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace `import { cn } from "@/lib/utils"`
|
||||
|
|
|
|||
80
packages/cli/src/utils/transformers/transform-tw-prefix.ts
Normal file
80
packages/cli/src/utils/transformers/transform-tw-prefix.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { SyntaxKind } from 'ts-morph'
|
||||
import { MagicString, parse } from '@vue/compiler-sfc'
|
||||
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
|
||||
import { splitClassName } from './transform-css-vars'
|
||||
import type { Transformer } from '@/src/utils/transformers'
|
||||
|
||||
export const transformTwPrefixes: Transformer = async ({
|
||||
sourceFile,
|
||||
config,
|
||||
}) => {
|
||||
const isVueFile = sourceFile.getFilePath().endsWith('vue')
|
||||
if (!config.tailwind?.prefix)
|
||||
return sourceFile
|
||||
|
||||
let template: SFCTemplateBlock | null = null
|
||||
|
||||
if (isVueFile) {
|
||||
const parsed = parse(sourceFile.getText())
|
||||
template = parsed.descriptor.template
|
||||
|
||||
if (!template)
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => {
|
||||
if (template && template.loc.start.offset >= node.getPos())
|
||||
return sourceFile
|
||||
|
||||
const attrName = sourceFile.getDescendantAtPos(node.getPos() - 2)?.getText()
|
||||
if (isVueFile && attrName !== 'class')
|
||||
return sourceFile
|
||||
|
||||
const value = node.getText()
|
||||
const hasClosingDoubleQuote = value.match(/"/g)?.length === 2
|
||||
if (value.search('\'') === -1 && hasClosingDoubleQuote) {
|
||||
const mapped = applyPrefix(value.replace(/"/g, ''), config.tailwind.prefix)
|
||||
node.replaceWithText(`"${mapped}"`)
|
||||
}
|
||||
else {
|
||||
const s = new MagicString(value)
|
||||
s.replace(/'(.*?)'/g, (substring) => {
|
||||
return `'${applyPrefix(substring.replace(/\'/g, ''), config.tailwind.prefix)}'`
|
||||
})
|
||||
node.replaceWithText(s.toString())
|
||||
}
|
||||
})
|
||||
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
export function applyPrefix(input: string, prefix: string = '') {
|
||||
const classNames = input.split(' ')
|
||||
const prefixed: string[] = []
|
||||
for (const className of classNames) {
|
||||
const [variant, value, modifier] = splitClassName(className)
|
||||
if (variant) {
|
||||
modifier
|
||||
? prefixed.push(`${variant}:${prefix}${value}/${modifier}`)
|
||||
: prefixed.push(`${variant}:${prefix}${value}`)
|
||||
}
|
||||
else {
|
||||
modifier
|
||||
? prefixed.push(`${prefix}${value}/${modifier}`)
|
||||
: prefixed.push(`${prefix}${value}`)
|
||||
}
|
||||
}
|
||||
return prefixed.join(' ')
|
||||
}
|
||||
|
||||
export function applyPrefixesCss(css: string, prefix: string) {
|
||||
const lines = css.split('\n')
|
||||
for (const line of lines) {
|
||||
if (line.includes('@apply')) {
|
||||
const originalTWCls = line.replace('@apply', '').trim()
|
||||
const prefixedTwCls = applyPrefix(originalTWCls, prefix)
|
||||
css = css.replace(originalTWCls, prefixedTwCls)
|
||||
}
|
||||
}
|
||||
return css
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { execa } from 'execa'
|
||||
import path from 'pathe'
|
||||
import { addDependency, addDevDependency } from 'nypm'
|
||||
import { afterEach, expect, test, vi } from 'vitest'
|
||||
|
||||
import { runInit } from '../../src/commands/init'
|
||||
import { getConfig } from '../../src/utils/get-config'
|
||||
import * as getPackageManger from '../../src/utils/get-package-manager'
|
||||
import * as registry from '../../src/utils/registry'
|
||||
|
||||
vi.mock('execa')
|
||||
vi.mock('nypm')
|
||||
vi.mock('fs/promises', () => ({
|
||||
writeFile: vi.fn(),
|
||||
mkdir: vi.fn(),
|
||||
|
|
@ -16,7 +15,6 @@ vi.mock('fs/promises', () => ({
|
|||
vi.mock('ora')
|
||||
|
||||
test('init config-full', async () => {
|
||||
vi.spyOn(getPackageManger, 'getPackageManager').mockResolvedValue('pnpm')
|
||||
vi.spyOn(registry, 'getRegistryBaseColor').mockResolvedValue({
|
||||
inlineColors: {},
|
||||
cssVars: {},
|
||||
|
|
@ -67,10 +65,8 @@ test('init config-full', async () => {
|
|||
expect.stringContaining("import { type ClassValue, clsx } from 'clsx'"),
|
||||
'utf8',
|
||||
)
|
||||
expect(execa).toHaveBeenCalledWith(
|
||||
'pnpm',
|
||||
expect(addDependency).toHaveBeenCalledWith(
|
||||
[
|
||||
'add',
|
||||
'tailwindcss-animate',
|
||||
'class-variance-authority',
|
||||
'clsx',
|
||||
|
|
@ -80,6 +76,7 @@ test('init config-full', async () => {
|
|||
],
|
||||
{
|
||||
cwd: targetDir,
|
||||
silent: true,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -88,7 +85,6 @@ test('init config-full', async () => {
|
|||
})
|
||||
|
||||
test('init config-partial', async () => {
|
||||
vi.spyOn(getPackageManger, 'getPackageManager').mockResolvedValue('npm')
|
||||
vi.spyOn(registry, 'getRegistryBaseColor').mockResolvedValue({
|
||||
inlineColors: {},
|
||||
cssVars: {},
|
||||
|
|
@ -139,10 +135,8 @@ test('init config-partial', async () => {
|
|||
expect.stringContaining("import { type ClassValue, clsx } from 'clsx'"),
|
||||
'utf8',
|
||||
)
|
||||
expect(execa).toHaveBeenCalledWith(
|
||||
'npm',
|
||||
expect(addDependency).toHaveBeenCalledWith(
|
||||
[
|
||||
'install',
|
||||
'tailwindcss-animate',
|
||||
'class-variance-authority',
|
||||
'clsx',
|
||||
|
|
@ -152,6 +146,7 @@ test('init config-partial', async () => {
|
|||
],
|
||||
{
|
||||
cwd: targetDir,
|
||||
silent: true,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
"cssVariables": true,
|
||||
"prefix": "tw-"
|
||||
},
|
||||
"aliases": {
|
||||
"utils": "~/lib/utils",
|
||||
"components": "~/components"
|
||||
"components": "~/components",
|
||||
"ui": "~/ui"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default {
|
|||
'./app/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
],
|
||||
prefix: \\"<%- prefix %>\\",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
|
|
@ -48,6 +49,7 @@ exports[`handle tailwind config template correctly 2`] = `
|
|||
export default {
|
||||
darkMode: [\\"class\\"],
|
||||
safelist: [\\"dark\\"],
|
||||
prefix: \\"<%- prefix %>\\",
|
||||
<% if (framework === 'vite') { %>
|
||||
content: [
|
||||
'./pages/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`transform tailwind prefix 1`] = `
|
||||
"const testVariants = cva(
|
||||
\\"tw-bg-background hover:tw-bg-muted tw-text-primary-foreground sm:focus:tw-text-accent-foreground\\",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
\\"tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/90\\",
|
||||
},
|
||||
size: {
|
||||
default: \\"tw-h-10 tw-px-4 tw-py-2\\",
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 2`] = `
|
||||
"<template>
|
||||
<div class=\\"tw-bg-background hover:tw-bg-muted tw-text-primary-foreground sm:focus:tw-text-accent-foreground\\">
|
||||
foo
|
||||
</div>
|
||||
</template>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 3`] = `
|
||||
"<template>
|
||||
<div class=\\"tw-bg-white hover:tw-bg-stone-100 tw-text-stone-50 sm:focus:tw-text-stone-900 dark:tw-bg-stone-950 dark:hover:tw-bg-stone-800 dark:tw-text-stone-900 dark:sm:focus:tw-text-stone-50\\">
|
||||
foo
|
||||
</div>
|
||||
</template>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 4`] = `
|
||||
"<template>
|
||||
<div id=\\"testing\\" v-bind=\\"props\\" @click=\\"handleSomething\\" :data-test=\\"true\\" :class=\\"cn('tw-bg-white hover:tw-bg-stone-100 dark:tw-bg-stone-950 dark:hover:tw-bg-stone-800', true && 'tw-text-stone-50 sm:focus:tw-text-stone-900 dark:tw-text-stone-900 dark:sm:focus:tw-text-stone-50')\\">
|
||||
foo
|
||||
</div>
|
||||
</template>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 5`] = `
|
||||
"@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 224 71.4% 4.1%;
|
||||
|
||||
--muted: 220 14.3% 95.9%;
|
||||
--muted-foreground: 220 8.9% 46.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 224 71.4% 4.1%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 224 71.4% 4.1%;
|
||||
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
|
||||
--primary: 220.9 39.3% 11%;
|
||||
--primary-foreground: 210 20% 98%;
|
||||
|
||||
--secondary: 220 14.3% 95.9%;
|
||||
--secondary-foreground: 220.9 39.3% 11%;
|
||||
|
||||
--accent: 220 14.3% 95.9%;
|
||||
--accent-foreground: 220.9 39.3% 11%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 20% 98%;
|
||||
|
||||
--ring: 217.9 10.6% 64.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71.4% 4.1%;
|
||||
--foreground: 210 20% 98%;
|
||||
|
||||
--muted: 215 27.9% 16.9%;
|
||||
--muted-foreground: 217.9 10.6% 64.9%;
|
||||
|
||||
--popover: 224 71.4% 4.1%;
|
||||
--popover-foreground: 210 20% 98%;
|
||||
|
||||
--card: 224 71.4% 4.1%;
|
||||
--card-foreground: 210 20% 98%;
|
||||
|
||||
--border: 215 27.9% 16.9%;
|
||||
--input: 215 27.9% 16.9%;
|
||||
|
||||
--primary: 210 20% 98%;
|
||||
--primary-foreground: 220.9 39.3% 11%;
|
||||
|
||||
--secondary: 215 27.9% 16.9%;
|
||||
--secondary-foreground: 210 20% 98%;
|
||||
|
||||
--accent: 215 27.9% 16.9%;
|
||||
--accent-foreground: 210 20% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 85.7% 97.3%;
|
||||
|
||||
--ring: 215 27.9% 16.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply tw-border-border;
|
||||
}
|
||||
body {
|
||||
@apply tw-bg-background tw-text-foreground;
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
applyColorMapping,
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
import baseColor from '../fixtures/colors/slate.json'
|
||||
|
||||
describe('split class', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
{
|
||||
input: 'bg-popover',
|
||||
output: [null, 'bg-popover', null],
|
||||
|
|
@ -50,7 +50,7 @@ describe('split class', () => {
|
|||
})
|
||||
|
||||
describe('apply color mapping', async () => {
|
||||
test.each([
|
||||
it.each([
|
||||
{
|
||||
input: 'bg-background text-foreground',
|
||||
output: 'bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50',
|
||||
|
|
@ -64,7 +64,7 @@ describe('apply color mapping', async () => {
|
|||
input:
|
||||
'text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive',
|
||||
output:
|
||||
'text-red-500 border-red-500/50 dark:border-red-500 [&>svg]:text-red-500 text-red-500 dark:text-red-900 dark:border-red-900/50 dark:dark:border-red-900 dark:[&>svg]:text-red-900 dark:text-red-900',
|
||||
'text-red-500 border-red-500/50 dark:border-red-500 [&>svg]:text-red-500 dark:text-red-900 dark:border-red-900/50 dark:dark:border-red-900 dark:[&>svg]:text-red-900',
|
||||
},
|
||||
{
|
||||
input:
|
||||
|
|
|
|||
42
packages/cli/test/utils/apply-prefix.test.ts
Normal file
42
packages/cli/test/utils/apply-prefix.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { applyPrefix } from '../../src/utils/transformers/transform-tw-prefix'
|
||||
|
||||
describe('apply tailwind prefix', () => {
|
||||
it.each([
|
||||
{
|
||||
input: 'bg-slate-800 text-gray-500',
|
||||
output: 'tw-bg-slate-800 tw-text-gray-500',
|
||||
},
|
||||
{
|
||||
input: 'hover:dark:bg-background dark:text-foreground',
|
||||
output: 'hover:dark:tw-bg-background dark:tw-text-foreground',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'rounded-lg border border-slate-200 bg-white text-slate-950 shadow-sm dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50',
|
||||
output:
|
||||
'tw-rounded-lg tw-border tw-border-slate-200 tw-bg-white tw-text-slate-950 tw-shadow-sm dark:tw-border-slate-800 dark:tw-bg-slate-950 dark:tw-text-slate-50',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'text-red-500 border-red-500/50 dark:border-red-500 [&>svg]:text-red-500 text-red-500 dark:text-red-900 dark:border-red-900/50 dark:dark:border-red-900 dark:[&>svg]:text-red-900 dark:text-red-900',
|
||||
output:
|
||||
'tw-text-red-500 tw-border-red-500/50 dark:tw-border-red-500 [&>svg]:tw-text-red-500 tw-text-red-500 dark:tw-text-red-900 dark:tw-border-red-900/50 dark:dark:tw-border-red-900 dark:[&>svg]:tw-text-red-900 dark:tw-text-red-900',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
||||
output:
|
||||
'tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center tw-rounded-full tw-bg-muted',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'absolute right-4 top-4 bg-primary rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
output:
|
||||
'tw-absolute tw-right-4 tw-top-4 tw-bg-primary tw-rounded-sm tw-opacity-70 tw-ring-offset-background tw-transition-opacity hover:tw-opacity-100 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-pointer-events-none data-[state=open]:tw-bg-secondary',
|
||||
},
|
||||
])(`applyTwPrefix($input) -> $output`, ({ input, output }) => {
|
||||
expect(applyPrefix(input, 'tw-')).toBe(output)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import path from 'node:path'
|
||||
import { expect, test } from 'vitest'
|
||||
import path from 'pathe'
|
||||
import { expect, it } from 'vitest'
|
||||
|
||||
import { getConfig, getRawConfig } from '../../src/utils/get-config'
|
||||
|
||||
test('get raw config', async () => {
|
||||
it('get raw config', async () => {
|
||||
expect(
|
||||
await getRawConfig(path.resolve(__dirname, '../fixtures/config-none')),
|
||||
).toEqual(null)
|
||||
|
|
@ -31,7 +31,7 @@ test('get raw config', async () => {
|
|||
).rejects.toThrowError()
|
||||
})
|
||||
|
||||
test('get config', async () => {
|
||||
it('get config', async () => {
|
||||
expect(
|
||||
await getConfig(path.resolve(__dirname, '../fixtures/config-none')),
|
||||
).toEqual(null)
|
||||
|
|
@ -71,6 +71,11 @@ test('get config', async () => {
|
|||
'../fixtures/config-partial',
|
||||
'./components',
|
||||
),
|
||||
ui: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-partial',
|
||||
'./components',
|
||||
),
|
||||
utils: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-partial',
|
||||
|
|
@ -89,9 +94,11 @@ test('get config', async () => {
|
|||
baseColor: 'zinc',
|
||||
css: 'src/app/globals.css',
|
||||
cssVariables: true,
|
||||
prefix: 'tw-',
|
||||
},
|
||||
aliases: {
|
||||
components: '~/components',
|
||||
ui: '~/ui',
|
||||
utils: '~/lib/utils',
|
||||
},
|
||||
framework: 'Vite',
|
||||
|
|
@ -111,6 +118,11 @@ test('get config', async () => {
|
|||
'../fixtures/config-full',
|
||||
'./src/components',
|
||||
),
|
||||
ui: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-full',
|
||||
'./src/ui',
|
||||
),
|
||||
utils: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-full',
|
||||
|
|
@ -152,6 +164,11 @@ test('get config', async () => {
|
|||
'../fixtures/config-js',
|
||||
'./components',
|
||||
),
|
||||
ui: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-js',
|
||||
'./components',
|
||||
),
|
||||
utils: path.resolve(__dirname, '../fixtures/config-js', './lib/utils'),
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import path from 'node:path'
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
import { getPackageManager } from '../../src/utils/get-package-manager'
|
||||
|
||||
test('get package manager', async () => {
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-yarn')),
|
||||
).toBe('yarn')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-npm')),
|
||||
).toBe('npm')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-pnpm')),
|
||||
).toBe('pnpm')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-bun')),
|
||||
).toBe('bun')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/next')),
|
||||
).toBe('pnpm')
|
||||
})
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import path from 'node:path'
|
||||
import path from 'pathe'
|
||||
import { type ConfigLoaderSuccessResult, loadConfig } from 'tsconfig-paths'
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { expect, test } from 'vitest'
|
||||
import { expect, it } from 'vitest'
|
||||
|
||||
import { transform } from '../../src/utils/transformers'
|
||||
import stone from '../fixtures/colors/stone.json'
|
||||
|
||||
test('transform css vars', async () => {
|
||||
it('transform css vars', async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: 'app.vue',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from 'node:path'
|
||||
import { resolve } from 'pathe'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { transform } from '../../src/utils/transformers'
|
||||
|
||||
|
|
|
|||
115
packages/cli/test/utils/transform-tw-prefix.test.ts
Normal file
115
packages/cli/test/utils/transform-tw-prefix.test.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { expect, it } from 'vitest'
|
||||
|
||||
import { transform } from '../../src/utils/transformers'
|
||||
import { applyPrefixesCss } from '../../src/utils/transformers/transform-tw-prefix'
|
||||
import stone from '../fixtures/colors/stone.json'
|
||||
|
||||
it('transform tailwind prefix', async () => {
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'test.ts',
|
||||
// raw: `const testVariants = cva(
|
||||
// 'bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground',
|
||||
// {
|
||||
// variants: {
|
||||
// variant: {
|
||||
// default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
// },
|
||||
// size: {
|
||||
// default: 'h-10 px-4 py-2',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// )`,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: 'stone',
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'app.vue',
|
||||
// raw: `<template>
|
||||
// <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">
|
||||
// foo
|
||||
// </div>
|
||||
// </template>
|
||||
// `,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: 'stone',
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'app.vue',
|
||||
// raw: `<template>
|
||||
// <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">
|
||||
// foo
|
||||
// </div>
|
||||
// </template>
|
||||
// `,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// cssVariables: false,
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: stone,
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'app.vue',
|
||||
// raw: `<template>
|
||||
// <div id="testing" v-bind="props" @click="handleSomething" :data-test="true" :class="cn('bg-background hover:bg-muted', true && 'text-primary-foreground sm:focus:text-accent-foreground')">
|
||||
// foo
|
||||
// </div>
|
||||
// </template>
|
||||
// `,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// cssVariables: false,
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: stone,
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// applyPrefixesCss(
|
||||
// '@tailwind base;\n@tailwind components;\n@tailwind utilities;\n \n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 224 71.4% 4.1%;\n \n --muted: 220 14.3% 95.9%;\n --muted-foreground: 220 8.9% 46.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 224 71.4% 4.1%;\n \n --card: 0 0% 100%;\n --card-foreground: 224 71.4% 4.1%;\n \n --border: 220 13% 91%;\n --input: 220 13% 91%;\n \n --primary: 220.9 39.3% 11%;\n --primary-foreground: 210 20% 98%;\n \n --secondary: 220 14.3% 95.9%;\n --secondary-foreground: 220.9 39.3% 11%;\n \n --accent: 220 14.3% 95.9%;\n --accent-foreground: 220.9 39.3% 11%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 217.9 10.6% 64.9%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 224 71.4% 4.1%;\n --foreground: 210 20% 98%;\n \n --muted: 215 27.9% 16.9%;\n --muted-foreground: 217.9 10.6% 64.9%;\n \n --popover: 224 71.4% 4.1%;\n --popover-foreground: 210 20% 98%;\n \n --card: 224 71.4% 4.1%;\n --card-foreground: 210 20% 98%;\n \n --border: 215 27.9% 16.9%;\n --input: 215 27.9% 16.9%;\n \n --primary: 210 20% 98%;\n --primary-foreground: 220.9 39.3% 11%;\n \n --secondary: 215 27.9% 16.9%;\n --secondary-foreground: 210 20% 98%;\n \n --accent: 215 27.9% 16.9%;\n --accent-foreground: 210 20% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 85.7% 97.3%;\n \n --ring: 215 27.9% 16.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}',
|
||||
// 'tw-',
|
||||
// ),
|
||||
// ).toMatchSnapshot()
|
||||
})
|
||||
|
|
@ -35,17 +35,17 @@
|
|||
"release": "pnpm run prepack && pnpm publish && git push --follow-tags"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.8.2",
|
||||
"oxc-parser": "^0.2.0"
|
||||
"@nuxt/kit": "^3.10.3",
|
||||
"oxc-parser": "^0.7.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",
|
||||
"@nuxt/module-builder": "^0.5.5",
|
||||
"@nuxt/schema": "^3.10.3",
|
||||
"@nuxt/test-utils": "^3.11.0",
|
||||
"@types/node": "^20.11.24",
|
||||
"nuxt": "^3.10.3",
|
||||
"vitest": "^0.33.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3610
pnpm-lock.yaml
3610
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user