Merge remote-tracking branch 'origin/dev' into pr/zernonia/619
This commit is contained in:
commit
71c5904a6a
12
.editorconfig
Normal file
12
.editorconfig
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -10,6 +10,7 @@
|
||||||
"source.fixAll.eslint": "explicit",
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.organizeImports": "never"
|
"source.organizeImports": "never"
|
||||||
},
|
},
|
||||||
|
"eslint.useFlatConfig": true,
|
||||||
"eslint.rules.customizations": [
|
"eslint.rules.customizations": [
|
||||||
{ "rule": "style/*", "severity": "off" },
|
{ "rule": "style/*", "severity": "off" },
|
||||||
{ "rule": "format/*", "severity": "off" },
|
{ "rule": "format/*", "severity": "off" },
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CircleHelp, Info, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
|
|
||||||
import { reactive, ref, watch } from 'vue'
|
|
||||||
import { codeToHtml } from 'shiki'
|
|
||||||
import { compileScript, parse, walk } from 'vue/compiler-sfc'
|
|
||||||
import MagicString from 'magic-string'
|
|
||||||
import { cssVariables } from '../config/shiki'
|
|
||||||
import StyleSwitcher from './StyleSwitcher.vue'
|
|
||||||
import Spinner from './Spinner.vue'
|
|
||||||
import BlockCopyButton from './BlockCopyButton.vue'
|
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { CircleHelp, Info, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { codeToHtml } from 'shiki'
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { compileScript, parse, walk } from 'vue/compiler-sfc'
|
||||||
|
import { cssVariables } from '../config/shiki'
|
||||||
|
import BlockCopyButton from './BlockCopyButton.vue'
|
||||||
|
import Spinner from './Spinner.vue'
|
||||||
|
import StyleSwitcher from './StyleSwitcher.vue'
|
||||||
|
|
||||||
// import { V0Button } from '@/components/v0-button'
|
// import { V0Button } from '@/components/v0-button'
|
||||||
import { Badge } from '@/lib/registry/new-york/ui/badge'
|
import { Badge } from '@/lib/registry/new-york/ui/badge'
|
||||||
|
|
@ -187,7 +187,7 @@ watch([style, codeConfig], async () => {
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The <span class="font-medium">Default</span> style has
|
The <span class="font-medium">Default</span> style has
|
||||||
larger inputs, uses lucide-react for icons and
|
larger inputs, uses lucide-vue-next for icons and
|
||||||
tailwindcss-animate for animations.
|
tailwindcss-animate for animations.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { CreditCard } from 'lucide-vue-next'
|
|
||||||
import RiAppleFill from '~icons/ri/apple-fill'
|
import RiAppleFill from '~icons/ri/apple-fill'
|
||||||
import RiPaypalFill from '~icons/ri/paypal-fill'
|
import RiPaypalFill from '~icons/ri/paypal-fill'
|
||||||
|
import { CreditCard } from 'lucide-vue-next'
|
||||||
|
|
||||||
type Color =
|
type Color =
|
||||||
| 'zinc'
|
| 'zinc'
|
||||||
|
|
@ -32,11 +32,11 @@ export const allColors: Color[] = [
|
||||||
'violet',
|
'violet',
|
||||||
]
|
]
|
||||||
|
|
||||||
interface Payment {
|
// interface Payment {
|
||||||
status: string
|
// status: string
|
||||||
email: string
|
// email: string
|
||||||
amount: number
|
// amount: number
|
||||||
}
|
// }
|
||||||
|
|
||||||
interface TeamMember {
|
interface TeamMember {
|
||||||
name: string
|
name: string
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,11 @@ export const docsConfig: DocsConfig = {
|
||||||
href: '/docs/components/sonner',
|
href: '/docs/components/sonner',
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Stepper',
|
||||||
|
href: '/docs/components/stepper',
|
||||||
|
label: 'New',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Switch',
|
title: 'Switch',
|
||||||
href: '/docs/components/switch',
|
href: '/docs/components/switch',
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { getParameters } from 'codesandbox/lib/api/define'
|
import type { Style } from '@/lib/registry/styles'
|
||||||
import sdk from '@stackblitz/sdk'
|
import sdk from '@stackblitz/sdk'
|
||||||
import { dependencies as deps } from '../../../package.json'
|
import { getParameters } from 'codesandbox/lib/api/define'
|
||||||
import { Index as demoIndex } from '../../../../www/__registry__'
|
import { Index as demoIndex } from '../../../../www/__registry__'
|
||||||
// @ts-expect-error ?raw
|
// @ts-expect-error ?raw
|
||||||
import tailwindConfigRaw from '../../../tailwind.config?raw'
|
import tailwindConfigRaw from '../../../tailwind.config?raw'
|
||||||
// @ts-expect-error ?raw
|
// @ts-expect-error ?raw
|
||||||
import cssRaw from '../../../../../packages/cli/test/fixtures/nuxt/assets/css/tailwind.css?raw'
|
import cssRaw from '../../../../../packages/cli/test/fixtures/nuxt/assets/css/tailwind.css?raw'
|
||||||
import type { Style } from '@/lib/registry/styles'
|
|
||||||
|
|
||||||
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
|
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
|
||||||
let files: Record<string, any> = {}
|
let files: Record<string, any> = {}
|
||||||
|
|
@ -92,7 +91,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
||||||
const iconPackage = style === 'default' ? 'lucide-vue-next' : '@radix-icons/vue'
|
const iconPackage = style === 'default' ? 'lucide-vue-next' : '@radix-icons/vue'
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
'vue': 'latest',
|
'vue': 'latest',
|
||||||
'radix-vue': deps['radix-vue'],
|
'radix-vue': 'latest',
|
||||||
'@radix-ui/colors': 'latest',
|
'@radix-ui/colors': 'latest',
|
||||||
'clsx': 'latest',
|
'clsx': 'latest',
|
||||||
'class-variance-authority': 'latest',
|
'class-variance-authority': 'latest',
|
||||||
|
|
|
||||||
|
|
@ -521,6 +521,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/DataTableDemoColumn.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/DataTableDemoColumn.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/DataTableDemoColumn.vue"],
|
files: ["../src/lib/registry/default/example/DataTableDemoColumn.vue"],
|
||||||
},
|
},
|
||||||
|
"DataTableReactiveDemo": {
|
||||||
|
name: "DataTableReactiveDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["button","checkbox","dropdown-menu","input","table","utils"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/DataTableReactiveDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/DataTableReactiveDemo.vue"],
|
||||||
|
},
|
||||||
"DatePickerDemo": {
|
"DatePickerDemo": {
|
||||||
name: "DatePickerDemo",
|
name: "DatePickerDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -570,6 +577,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/DialogDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/DialogDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/DialogDemo.vue"],
|
files: ["../src/lib/registry/default/example/DialogDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"DialogForm": {
|
||||||
|
name: "DialogForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["button","form","dialog","input","toast"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/DialogForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/DialogForm.vue"],
|
||||||
|
},
|
||||||
"DialogScrollBodyDemo": {
|
"DialogScrollBodyDemo": {
|
||||||
name: "DialogScrollBodyDemo",
|
name: "DialogScrollBodyDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -752,13 +766,6 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/NavigationMenuDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/NavigationMenuDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/NavigationMenuDemo.vue"],
|
files: ["../src/lib/registry/default/example/NavigationMenuDemo.vue"],
|
||||||
},
|
},
|
||||||
"NavigationMenuDemoItem": {
|
|
||||||
name: "NavigationMenuDemoItem",
|
|
||||||
type: "components:example",
|
|
||||||
registryDependencies: ["utils","navigation-menu"],
|
|
||||||
component: () => import("../src/lib/registry/default/example/NavigationMenuDemoItem.vue").then((m) => m.default),
|
|
||||||
files: ["../src/lib/registry/default/example/NavigationMenuDemoItem.vue"],
|
|
||||||
},
|
|
||||||
"NumberFieldCurrency": {
|
"NumberFieldCurrency": {
|
||||||
name: "NumberFieldCurrency",
|
name: "NumberFieldCurrency",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -997,6 +1004,34 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/SonnerWithDialog.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/SonnerWithDialog.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/SonnerWithDialog.vue"],
|
files: ["../src/lib/registry/default/example/SonnerWithDialog.vue"],
|
||||||
},
|
},
|
||||||
|
"StepperDemo": {
|
||||||
|
name: "StepperDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/StepperDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/StepperDemo.vue"],
|
||||||
|
},
|
||||||
|
"StepperForm": {
|
||||||
|
name: "StepperForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper","form","select","input","button","toast"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/StepperForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/StepperForm.vue"],
|
||||||
|
},
|
||||||
|
"StepperHorizental": {
|
||||||
|
name: "StepperHorizental",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper","button"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/StepperHorizental.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/StepperHorizental.vue"],
|
||||||
|
},
|
||||||
|
"StepperVertical": {
|
||||||
|
name: "StepperVertical",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper","button"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/StepperVertical.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/StepperVertical.vue"],
|
||||||
|
},
|
||||||
"SwitchDemo": {
|
"SwitchDemo": {
|
||||||
name: "SwitchDemo",
|
name: "SwitchDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -1025,6 +1060,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/TabsDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/TabsDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/TabsDemo.vue"],
|
files: ["../src/lib/registry/default/example/TabsDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"TabsVerticalDemo": {
|
||||||
|
name: "TabsVerticalDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["button","card","input","label","tabs"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/TabsVerticalDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/TabsVerticalDemo.vue"],
|
||||||
|
},
|
||||||
"TagsInputComboboxDemo": {
|
"TagsInputComboboxDemo": {
|
||||||
name: "TagsInputComboboxDemo",
|
name: "TagsInputComboboxDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -1039,6 +1081,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/TagsInputDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/TagsInputDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/TagsInputDemo.vue"],
|
files: ["../src/lib/registry/default/example/TagsInputDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"TagsInputFormDemo": {
|
||||||
|
name: "TagsInputFormDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["tags-input","button","form","toast"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/TagsInputFormDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/TagsInputFormDemo.vue"],
|
||||||
|
},
|
||||||
"TextareaDemo": {
|
"TextareaDemo": {
|
||||||
name: "TextareaDemo",
|
name: "TextareaDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -1971,6 +2020,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/DataTableDemoColumn.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/DataTableDemoColumn.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/DataTableDemoColumn.vue"],
|
files: ["../src/lib/registry/new-york/example/DataTableDemoColumn.vue"],
|
||||||
},
|
},
|
||||||
|
"DataTableReactiveDemo": {
|
||||||
|
name: "DataTableReactiveDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["button","checkbox","dropdown-menu","input","table","utils"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/DataTableReactiveDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/DataTableReactiveDemo.vue"],
|
||||||
|
},
|
||||||
"DatePickerDemo": {
|
"DatePickerDemo": {
|
||||||
name: "DatePickerDemo",
|
name: "DatePickerDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -2020,6 +2076,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/DialogDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/DialogDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/DialogDemo.vue"],
|
files: ["../src/lib/registry/new-york/example/DialogDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"DialogForm": {
|
||||||
|
name: "DialogForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["button","form","dialog","input","toast"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/DialogForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/DialogForm.vue"],
|
||||||
|
},
|
||||||
"DialogScrollBodyDemo": {
|
"DialogScrollBodyDemo": {
|
||||||
name: "DialogScrollBodyDemo",
|
name: "DialogScrollBodyDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -2202,13 +2265,6 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/NavigationMenuDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/NavigationMenuDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/NavigationMenuDemo.vue"],
|
files: ["../src/lib/registry/new-york/example/NavigationMenuDemo.vue"],
|
||||||
},
|
},
|
||||||
"NavigationMenuDemoItem": {
|
|
||||||
name: "NavigationMenuDemoItem",
|
|
||||||
type: "components:example",
|
|
||||||
registryDependencies: ["utils","navigation-menu"],
|
|
||||||
component: () => import("../src/lib/registry/new-york/example/NavigationMenuDemoItem.vue").then((m) => m.default),
|
|
||||||
files: ["../src/lib/registry/new-york/example/NavigationMenuDemoItem.vue"],
|
|
||||||
},
|
|
||||||
"NumberFieldCurrency": {
|
"NumberFieldCurrency": {
|
||||||
name: "NumberFieldCurrency",
|
name: "NumberFieldCurrency",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -2447,6 +2503,34 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/SonnerWithDialog.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/SonnerWithDialog.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/SonnerWithDialog.vue"],
|
files: ["../src/lib/registry/new-york/example/SonnerWithDialog.vue"],
|
||||||
},
|
},
|
||||||
|
"StepperDemo": {
|
||||||
|
name: "StepperDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/StepperDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/StepperDemo.vue"],
|
||||||
|
},
|
||||||
|
"StepperForm": {
|
||||||
|
name: "StepperForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper","form","select","input","button","toast"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/StepperForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/StepperForm.vue"],
|
||||||
|
},
|
||||||
|
"StepperHorizental": {
|
||||||
|
name: "StepperHorizental",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper","button"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/StepperHorizental.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/StepperHorizental.vue"],
|
||||||
|
},
|
||||||
|
"StepperVertical": {
|
||||||
|
name: "StepperVertical",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["stepper","button"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/StepperVertical.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/StepperVertical.vue"],
|
||||||
|
},
|
||||||
"SwitchDemo": {
|
"SwitchDemo": {
|
||||||
name: "SwitchDemo",
|
name: "SwitchDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -2475,6 +2559,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/TabsDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/TabsDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/TabsDemo.vue"],
|
files: ["../src/lib/registry/new-york/example/TabsDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"TabsVerticalDemo": {
|
||||||
|
name: "TabsVerticalDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["button","card","input","label","tabs"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/TabsVerticalDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/TabsVerticalDemo.vue"],
|
||||||
|
},
|
||||||
"TagsInputComboboxDemo": {
|
"TagsInputComboboxDemo": {
|
||||||
name: "TagsInputComboboxDemo",
|
name: "TagsInputComboboxDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -2489,6 +2580,13 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/TagsInputDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/TagsInputDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/TagsInputDemo.vue"],
|
files: ["../src/lib/registry/new-york/example/TagsInputDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"TagsInputFormDemo": {
|
||||||
|
name: "TagsInputFormDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["tags-input","button","form","toast"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/TagsInputFormDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/TagsInputFormDemo.vue"],
|
||||||
|
},
|
||||||
"TextareaDemo": {
|
"TextareaDemo": {
|
||||||
name: "TextareaDemo",
|
name: "TextareaDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
|
||||||
|
|
@ -17,65 +17,65 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@internationalized/date": "^3.5.4",
|
"@internationalized/date": "^3.5.5",
|
||||||
"@radix-icons/vue": "^1.0.0",
|
"@radix-icons/vue": "^1.0.0",
|
||||||
"@stackblitz/sdk": "^1.10.0",
|
"@stackblitz/sdk": "^1.11.0",
|
||||||
"@tanstack/vue-table": "^8.17.3",
|
"@tanstack/vue-table": "^8.20.5",
|
||||||
"@unovis/ts": "^1.4.1",
|
"@unovis/ts": "^1.4.4",
|
||||||
"@unovis/vue": "^1.4.1",
|
"@unovis/vue": "^1.4.4",
|
||||||
"@vee-validate/zod": "^4.13.1",
|
"@vee-validate/zod": "^4.13.2",
|
||||||
"@vueuse/core": "^10.11.0",
|
"@vueuse/core": "^11.1.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"codesandbox": "^2.2.3",
|
"codesandbox": "^2.2.3",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-autoplay": "^8.1.5",
|
"embla-carousel-autoplay": "^8.3.0",
|
||||||
"embla-carousel-vue": "^8.1.5",
|
"embla-carousel-vue": "^8.3.0",
|
||||||
"lucide-vue-next": "^0.383.0",
|
"lucide-vue-next": "^0.441.0",
|
||||||
"magic-string": "^0.30.10",
|
"magic-string": "^0.30.11",
|
||||||
"radix-vue": "^1.8.4",
|
"radix-vue": "catalog:",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
"vaul-vue": "^0.2.0",
|
"vaul-vue": "^0.2.0",
|
||||||
"vee-validate": "4.13.1",
|
"vee-validate": "4.13.2",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.5.6",
|
||||||
"vue-sonner": "^1.1.2",
|
"vue-sonner": "^1.1.5",
|
||||||
"vue-wrap-balancer": "^1.1.3",
|
"vue-wrap-balancer": "^1.2.1",
|
||||||
"zod": "^3.23.8"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/traverse": "^7.24.7",
|
"@babel/traverse": "^7.25.6",
|
||||||
"@iconify-json/gravity-ui": "^1.1.3",
|
"@iconify-json/gravity-ui": "^1.1.3",
|
||||||
"@iconify-json/lucide": "^1.1.190",
|
"@iconify-json/lucide": "^1.1.198",
|
||||||
"@iconify-json/ph": "^1.1.13",
|
"@iconify-json/ph": "^1.1.13",
|
||||||
"@iconify-json/radix-icons": "^1.1.14",
|
"@iconify-json/radix-icons": "^1.1.14",
|
||||||
"@iconify-json/ri": "^1.1.20",
|
"@iconify-json/ri": "^1.1.21",
|
||||||
"@iconify-json/simple-icons": "^1.1.104",
|
"@iconify-json/simple-icons": "^1.1.108",
|
||||||
"@iconify-json/tabler": "^1.1.113",
|
"@iconify-json/tabler": "^1.1.116",
|
||||||
"@iconify/vue": "^4.1.2",
|
"@iconify/vue": "^4.1.2",
|
||||||
"@oxc-parser/wasm": "^0.14.0",
|
"@oxc-parser/wasm": "catalog:",
|
||||||
"@shikijs/transformers": "^1.7.0",
|
"@shikijs/transformers": "^1.17.7",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^20.14.4",
|
"@types/node": "^22.5.5",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||||
"@vue/compiler-core": "^3.4.29",
|
"@vue/compiler-core": "^3.5.6",
|
||||||
"@vue/compiler-dom": "^3.4.29",
|
"@vue/compiler-dom": "^3.5.6",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.20",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"rimraf": "^5.0.7",
|
"rimraf": "^6.0.1",
|
||||||
"shiki": "^1.7.0",
|
"shiki": "^1.17.7",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.12",
|
||||||
"tsx": "^4.15.6",
|
"tsx": "^4.19.1",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.6.2",
|
||||||
"unplugin-icons": "^0.19.0",
|
"unplugin-icons": "^0.19.3",
|
||||||
"vitepress": "^1.2.3",
|
"vitepress": "^1.3.4",
|
||||||
"vue-component-meta": "^2.0.21",
|
"vue-component-meta": "^2.1.6",
|
||||||
"vue-tsc": "^2.0.21"
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,6 @@ The Sonner component is provided by [vue-sonner](https://vue-sonner.vercel.app/)
|
||||||
|
|
||||||
### New Component - Carousel
|
### New Component - Carousel
|
||||||
|
|
||||||
[`Carousel`](/docs/components/toggle-group.html) - A carousel with motion and swipe built using [Embla](https://www.embla-carousel.com/) library.
|
[`Carousel`](/docs/components/carousel.html) - A carousel with motion and swipe built using [Embla](https://www.embla-carousel.com/) library.
|
||||||
|
|
||||||
<ComponentPreview name="CarouselDemo" />
|
<ComponentPreview name="CarouselDemo" />
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ import { AspectRatio } from '@/components/ui/aspect-ratio'
|
||||||
<template>
|
<template>
|
||||||
<div class="w-[450px]">
|
<div class="w-[450px]">
|
||||||
<AspectRatio :ratio="16 / 9">
|
<AspectRatio :ratio="16 / 9">
|
||||||
<img src="..." alt="Image" class="rounded-md object-cover">
|
<img src="..." alt="Image" class="rounded-md object-cover w-full h-full">
|
||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ Use a custom component as `slot` for `<BreadcrumbSeparator />` to create a custo
|
||||||
|
|
||||||
```vue showLineNumbers {2,20-22}
|
```vue showLineNumbers {2,20-22}
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Slash } from 'lucide-react'
|
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
|
|
@ -66,6 +65,7 @@ import {
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from '@/components/ui/breadcrumb'
|
} from '@/components/ui/breadcrumb'
|
||||||
|
import { Slash } from 'lucide-vue-next'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -99,6 +99,8 @@ You can compose `<BreadcrumbItem />` with a `<DropdownMenu />` to create a dropd
|
||||||
|
|
||||||
```vue showLineNumbers {2-7,16-26}
|
```vue showLineNumbers {2-7,16-26}
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { BreadcrumbItem } from '@/components/ui/breadcrumb'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -106,8 +108,6 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/lib/components/ui/dropdown-menu'
|
} from '@/lib/components/ui/dropdown-menu'
|
||||||
|
|
||||||
import { BreadcrumbItem } from '@/components/ui/breadcrumb'
|
|
||||||
|
|
||||||
import ChevronDownIcon from '~icons/radix-icons/chevron-down'
|
import ChevronDownIcon from '~icons/radix-icons/chevron-down'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -169,13 +169,13 @@ To use a custom link component from your routing library, you can use the `asChi
|
||||||
|
|
||||||
```vue showLineNumbers {15-19}
|
```vue showLineNumbers {15-19}
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbLink,
|
BreadcrumbLink,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
} from '@/components/ui/breadcrumb'
|
} from '@/components/ui/breadcrumb'
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ If you're looking for a range calendar, check out the [Range Calendar](/docs/com
|
||||||
```bash
|
```bash
|
||||||
npx shadcn-vue@latest add calendar
|
npx shadcn-vue@latest add calendar
|
||||||
```
|
```
|
||||||
|
::: tip
|
||||||
|
The component depends on the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package, which solves a lot of the problems that come with working with dates and times in JavaScript.
|
||||||
|
Check [Dates & Times in Radix Vue](https://www.radix-vue.com/guides/dates.html) for more information and installation instructions.
|
||||||
|
:::
|
||||||
|
|
||||||
## Datepicker
|
## Datepicker
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ primitive: https://tanstack.com/table/v8/docs/guide/introduction
|
||||||
|
|
||||||
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
|
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
|
||||||
|
|
||||||
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/v8/docs/guide/introduction#what-is-headless-ui) provides.
|
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/latest/docs/introduction#what-is-headless-ui) provides.
|
||||||
|
|
||||||
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
|
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
|
||||||
|
|
||||||
|
|
@ -55,6 +55,20 @@ npm install @tanstack/vue-table
|
||||||
|
|
||||||
<ComponentPreview name="DataTableColumnPinningDemo" />
|
<ComponentPreview name="DataTableColumnPinningDemo" />
|
||||||
|
|
||||||
|
### Reactive Table
|
||||||
|
|
||||||
|
A reactive table was added in `v8.20.0` of the TanStack Table. You can see the [docs](https://tanstack.com/table/latest/docs/framework/vue/guide/table-state#using-reactive-data) for more information. We added an example where we are randomizing `status` column. One main point is that you need to mutate **full** data, as it is a `shallowRef` object.
|
||||||
|
|
||||||
|
> __*⚠️ `shallowRef` is used under the hood for performance reasons, meaning that the data is not deeply reactive, only the `.value` is. To update the data you have to mutate the data directly.*__
|
||||||
|
|
||||||
|
Relative PR: [Tanstack/table #5687](https://github.com/TanStack/table/pull/5687#issuecomment-2281067245)
|
||||||
|
|
||||||
|
If you want to mutate `props.data`, you should use [`defineModel`](https://vuejs.org/api/sfc-script-setup.html#definemodel).
|
||||||
|
|
||||||
|
There is no difference between using `ref` or `shallowRef` for your data object; it will be automatically mutated by the TanStack Table to `shallowRef`.
|
||||||
|
|
||||||
|
<ComponentPreview name="DataTableReactiveDemo" />
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
We are going to build a table to show recent payments. Here's what our data looks like:
|
We are going to build a table to show recent payments. Here's what our data looks like:
|
||||||
|
|
@ -149,12 +163,6 @@ Next, we'll create a `<DataTable />` component to render our table.
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts" generic="TData, TValue">
|
<script setup lang="ts" generic="TData, TValue">
|
||||||
import type { ColumnDef } from '@tanstack/vue-table'
|
import type { ColumnDef } from '@tanstack/vue-table'
|
||||||
import {
|
|
||||||
FlexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
useVueTable,
|
|
||||||
} from '@tanstack/vue-table'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
|
@ -164,6 +172,12 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table'
|
} from '@/components/ui/table'
|
||||||
|
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, TValue>[]
|
||||||
data: TData[]
|
data: TData[]
|
||||||
|
|
@ -227,9 +241,9 @@ Finally, we'll render our table in our index component.
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Payment } from './components/columns'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { columns } from './components/columns'
|
import { columns } from './components/columns'
|
||||||
import type { Payment } from './components/columns'
|
|
||||||
import DataTable from './components/DataTable.vue'
|
import DataTable from './components/DataTable.vue'
|
||||||
|
|
||||||
const data = ref<Payment[]>([])
|
const data = ref<Payment[]>([])
|
||||||
|
|
@ -303,9 +317,9 @@ Let's add row actions to our table. We'll use a `<Dropdown />` component for thi
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MoreHorizontal } from 'lucide-vue-next'
|
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
|
import { MoreHorizontal } from 'lucide-vue-next'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
payment: {
|
payment: {
|
||||||
|
|
@ -344,8 +358,8 @@ function copy(id: string) {
|
||||||
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { ColumnDef } from '@tanstack/vue-table'
|
|
||||||
import DropdownAction from '@/components/DataTableDropDown.vue'
|
import DropdownAction from '@/components/DataTableDropDown.vue'
|
||||||
|
import { ColumnDef } from '@tanstack/vue-table'
|
||||||
|
|
||||||
export const columns: ColumnDef<Payment>[] = [
|
export const columns: ColumnDef<Payment>[] = [
|
||||||
// ...
|
// ...
|
||||||
|
|
@ -451,12 +465,12 @@ Let's make the email column sortable.
|
||||||
### Add the following into your `utils` file
|
### Add the following into your `utils` file
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { type ClassValue, clsx } from 'clsx'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
import type { Updater } from '@tanstack/vue-table'
|
import type { Updater } from '@tanstack/vue-table'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
|
import { type ClassValue, clsx } from 'clsx'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
@ -866,11 +880,237 @@ This adds a checkbox to each row and a checkbox in the header to select all rows
|
||||||
|
|
||||||
You can show the number of selected rows using the `table.getFilteredSelectedRowModel()` API.
|
You can show the number of selected rows using the `table.getFilteredSelectedRowModel()` API.
|
||||||
|
|
||||||
```vue
|
```vue:line-numbers {8-11}
|
||||||
<div class="flex-1 text-sm text-muted-foreground">
|
<template>
|
||||||
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
<div>
|
||||||
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
<div class="border rounded-md">
|
||||||
</div>
|
<Table />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end space-x-2 py-4">
|
||||||
|
<div class="flex-1 text-sm text-muted-foreground">
|
||||||
|
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
||||||
|
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div class="space-x-2">
|
||||||
|
<PaginationButtons />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
## Expanding
|
||||||
|
|
||||||
|
Let's make rows expandable.
|
||||||
|
|
||||||
|
### Update `<DataTable>`
|
||||||
|
|
||||||
|
```vue:line-numbers {7,30,43,52,57,63,103-116}
|
||||||
|
<script setup lang="ts" generic="TData, TValue">
|
||||||
|
import type {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
ExpandedState,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
|
||||||
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
|
||||||
|
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from "@tanstack/vue-table"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
columns: ColumnDef<TData, TValue>[]
|
||||||
|
data: TData[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sorting = ref<SortingState>([])
|
||||||
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
|
const table = useVueTable({
|
||||||
|
get data() { return props.data },
|
||||||
|
get columns() { return props.columns },
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
|
state: {
|
||||||
|
get sorting() { return sorting.value },
|
||||||
|
get columnFilters() { return columnFilters.value },
|
||||||
|
get columnVisibility() { return columnVisibility.value },
|
||||||
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center py-4">
|
||||||
|
<Input class="max-w-sm" placeholder="Filter emails..."
|
||||||
|
:model-value="table.getColumn('email')?.getFilterValue() as string"
|
||||||
|
@update:model-value=" table.getColumn('email')?.setFilterValue($event)" />
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button variant="outline" class="ml-auto">
|
||||||
|
Columns
|
||||||
|
<ChevronDown class="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())" :key="column.id"
|
||||||
|
class="capitalize" :checked="column.getIsVisible()" @update:checked="(value) => {
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}">
|
||||||
|
{{ column.id }}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div class="border rounded-md">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||||
|
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
||||||
|
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
|
||||||
|
:props="header.getContext()" />
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
|
<TableRow :data-state="row.getIsSelected() ? 'selected' : undefined">
|
||||||
|
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
|
{{ JSON.stringify(row.original) }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell :colSpan="columns.length" class="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add the expand action to the `DataTableDropDown.vue` component
|
||||||
|
|
||||||
|
```vue:line-numbers {12-14,34-36}
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { MoreHorizontal } from 'lucide-vue-next'
|
||||||
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
payment: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'expand'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function copy(id: string) {
|
||||||
|
navigator.clipboard.writeText(id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button variant="ghost" class="w-8 h-8 p-0">
|
||||||
|
<span class="sr-only">Open menu</span>
|
||||||
|
<MoreHorizontal class="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem @click="copy(payment.id)">
|
||||||
|
Copy payment ID
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem @click="$emit('expand')">
|
||||||
|
Expand
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Make rows expandable
|
||||||
|
|
||||||
|
Now we can update the action cell to add the expand control.
|
||||||
|
|
||||||
|
```vue:line-numbers {11}
|
||||||
|
<script setup lang="ts">
|
||||||
|
export const columns: ColumnDef<Payment>[] = [
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const payment = row.original
|
||||||
|
|
||||||
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ import {
|
||||||
|
|
||||||
<ComponentPreview name="DialogScrollOverlayDemo" />
|
<ComponentPreview name="DialogScrollOverlayDemo" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="DialogForm" />
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or `Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836).
|
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or `Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836).
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,14 @@ npx shadcn-vue@latest add number-field
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
import {
|
import {
|
||||||
NumberField,
|
NumberField,
|
||||||
NumberFieldContent,
|
NumberFieldContent,
|
||||||
NumberFieldDecrement,
|
NumberFieldDecrement,
|
||||||
NumberFieldIncrement,
|
NumberFieldIncrement,
|
||||||
NumberFieldInput,
|
NumberFieldInput,
|
||||||
} from '@/lib/registry/default/ui/number-field'
|
} from '@/components/ui/number-field'
|
||||||
import { Label } from '@/lib/registry/default/ui/label'
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,6 @@ import { Separator } from '@/components/ui/separator'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Separator />
|
<Separator label="Or" />
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
|
||||||
64
apps/www/src/content/docs/components/stepper.md
Normal file
64
apps/www/src/content/docs/components/stepper.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
title: Stepper
|
||||||
|
description: A set of steps that are used to indicate progress through a multi-step process.
|
||||||
|
source: apps/www/src/lib/registry/default/ui/stepper
|
||||||
|
primitive: https://www.radix-vue.com/components/stepper.html
|
||||||
|
---
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperDemo" />
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn-vue@latest add stepper
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Stepper,
|
||||||
|
StepperDescription,
|
||||||
|
StepperIndicator,
|
||||||
|
StepperItem,
|
||||||
|
StepperSeparator,
|
||||||
|
StepperTitle,
|
||||||
|
StepperTrigger,
|
||||||
|
} from '@/components/ui/stepper'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper>
|
||||||
|
<StepperItem :step="1">
|
||||||
|
<StepperTrigger>
|
||||||
|
<StepperIndicator>1</StepperIndicator>
|
||||||
|
<StepperTitle>Step 1</StepperTitle>
|
||||||
|
<StepperDescription>This is the first step</StepperDescription>
|
||||||
|
</StepperTrigger>
|
||||||
|
<StepperSeparator />
|
||||||
|
</StepperItem>
|
||||||
|
<StepperItem :step="2">
|
||||||
|
<StepperTrigger>
|
||||||
|
<StepperIndicator>2</StepperIndicator>
|
||||||
|
<StepperTitle>Step 2</StepperTitle>
|
||||||
|
<StepperDescription>This is the second step</StepperDescription>
|
||||||
|
</StepperTrigger>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Horizontal
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperHorizental" />
|
||||||
|
|
||||||
|
### Vertical
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperVertical" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperForm" />
|
||||||
|
|
@ -39,3 +39,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Vertical
|
||||||
|
|
||||||
|
<ComponentPreview name="TabsVerticalDemo" />
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,7 @@ npx shadcn-vue@latest add tags-input
|
||||||
### Tags with Combobox
|
### Tags with Combobox
|
||||||
|
|
||||||
<ComponentPreview name="TagsInputComboboxDemo" />
|
<ComponentPreview name="TagsInputComboboxDemo" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="TagsInputFormDemo" />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@
|
||||||
title: Contribution
|
title: Contribution
|
||||||
description: Learn on how to contribute to shadcn/vue.
|
description: Learn on how to contribute to shadcn/vue.
|
||||||
---
|
---
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from "@/lib/registry/new-york/ui/button"
|
||||||
|
|
||||||
|
const latestSyncCommitTag = "06cc0cdf3d080555d26abbe6639f2d7f6341ec73"
|
||||||
|
|
||||||
|
const latestSyncCommitUrl = `https://github.com/shadcn-ui/ui/commit/${latestSyncCommitTag}`
|
||||||
|
const diffUrl = `https://github.com/shadcn-ui/ui/compare/${latestSyncCommitTag}...main`
|
||||||
|
</script>
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Thanks for your interest in contributing to shadcn-vue.com. We're happy to have you here.
|
Thanks for your interest in contributing to shadcn-vue.com. We're happy to have you here.
|
||||||
|
|
@ -212,9 +221,9 @@ Take a look at `DrawerDescription.vue`.
|
||||||
```vue
|
```vue
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { DrawerDescriptionProps } from 'vaul-vue'
|
import type { DrawerDescriptionProps } from 'vaul-vue'
|
||||||
import { DrawerDescription } from 'vaul-vue'
|
|
||||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { DrawerDescription } from 'vaul-vue'
|
||||||
|
import { computed, type HtmlHTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -261,9 +270,9 @@ Take a look at `AccordionItem.vue`
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -298,9 +307,9 @@ Let's take a look at `Button.vue`
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from 'vue'
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
||||||
import { type ButtonVariants, buttonVariants } from '.'
|
import { type ButtonVariants, buttonVariants } from '.'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
interface Props extends PrimitiveProps {
|
||||||
variant?: ButtonVariants['variant']
|
variant?: ButtonVariants['variant']
|
||||||
|
|
@ -326,6 +335,25 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
|
|
||||||
You'll need to extend `PrimitiveProps` in your props to support `Primitive` component. In most cases you would also need a default value for [`as`](https://www.radix-vue.com/utilities/primitive.html#changing-as-value) property.
|
You'll need to extend `PrimitiveProps` in your props to support `Primitive` component. In most cases you would also need a default value for [`as`](https://www.radix-vue.com/utilities/primitive.html#changing-as-value) property.
|
||||||
|
|
||||||
|
## Updating with `shadcn/ui`
|
||||||
|
|
||||||
|
`shadcn/vue` is an unofficial, community-led Vue port of `shadcn/ui`, as time goes by, they might get out of sync.
|
||||||
|
|
||||||
|
As of today, we are in sync with this <a :href="latestSyncCommitUrl" target="_blank">commit</a> of `shadcn/ui`.
|
||||||
|
|
||||||
|
Click on the following link to check if there are newer commits that we should be synced with.
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a :href="diffUrl" target="_blank">
|
||||||
|
<Button>
|
||||||
|
Check Diff
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
1. There are no changes - If you see "There isn’t anything to compare", nothing needs to be done as we are synced with latest version.
|
||||||
|
2. If there are changes, you should review thoese changes and try to apply them on `shadcn/vue` codebase and create a PR, remember to update the `latestSyncCommitTag` in [this file](https://github.com/radix-vue/shadcn-vue/blob/dev/apps/www/src/content/docs/contribution.md) too.
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
Here are some tools and techniques that can help you debug more effectively while contributing to `shadcn/vue` or developing your own projects.
|
Here are some tools and techniques that can help you debug more effectively while contributing to `shadcn/vue` or developing your own projects.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,10 @@ We're using [`useColorMode`](https://vueuse.org/core/usecolormode/) from [`@vueu
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useColorMode } from '@vueuse/core'
|
|
||||||
import { Icon } from '@iconify/vue'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { useColorMode } from '@vueuse/core'
|
||||||
|
|
||||||
const mode = useColorMode()
|
const mode = useColorMode()
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -100,7 +100,7 @@ Place a mode toggle on your site to toggle between light and dark mode.
|
||||||
```astro title="src/pages/index.astro"
|
```astro title="src/pages/index.astro"
|
||||||
---
|
---
|
||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import { ModeToggle } from '@/components/ModeToggle.vue';
|
import ModeToggle from '@/components/ModeToggle.vue';
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Inline script -->
|
<!-- Inline script -->
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ description: Adding dark mode to your nuxt app.
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
|
|
||||||
### Install Dependencies
|
<!-- ### Install Dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -D @nuxtjs/color-mode
|
npm install -D @nuxtjs/color-mode
|
||||||
|
|
@ -25,24 +25,26 @@ export default defineNuxtConfig({
|
||||||
classSuffix: ''
|
classSuffix: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
``` -->
|
||||||
|
|
||||||
|
### Add a mode toggle
|
||||||
|
|
||||||
|
Place a mode toggle on your site to toggle between light and dark mode.
|
||||||
|
|
||||||
|
The `@nuxtjs/color-mode` module is automatically installed and configured during the installation of the `shadcn-nuxt` module, so you literally have nothing to do.
|
||||||
|
|
||||||
|
We're using [`useColorMode`](https://color-mode.nuxtjs.org/#usage) from [`Nuxt Color Mode`](https://color-mode.nuxtjs.org/).
|
||||||
|
|
||||||
Optional, to include icons for theme button.
|
Optional, to include icons for theme button.
|
||||||
```bash
|
```bash
|
||||||
npm install -D @iconify/vue @iconify-json/radix-icons
|
npm install -D @iconify/vue @iconify-json/radix-icons
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add a mode toggle
|
|
||||||
|
|
||||||
Place a mode toggle on your site to toggle between light and dark mode.
|
|
||||||
|
|
||||||
We're using [`useColorMode`](https://color-mode.nuxtjs.org/#usage) from [`Nuxt Color Mode`](https://color-mode.nuxtjs.org/).
|
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from '@iconify/vue'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,12 @@ We're using [`useColorMode`](https://vueuse.org/core/usecolormode/) from [`@vueu
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useColorMode } from '@vueuse/core'
|
|
||||||
import { Icon } from '@iconify/vue'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { useColorMode } from '@vueuse/core'
|
||||||
|
|
||||||
|
// Pass { disableTransition: false } to enable transitions
|
||||||
const mode = useColorMode()
|
const mode = useColorMode()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ Write configuration to components.json. Proceed? > Y/n
|
||||||
|
|
||||||
### Import the globals.css file
|
### Import the globals.css file
|
||||||
|
|
||||||
Import the `globals.css` file in the `src/index.astro` file:
|
Import the `globals.css` file in the `src/pages/index.astro` file:
|
||||||
|
|
||||||
```ts:line-numbers {2}
|
```ts:line-numbers {2}
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ description: Install and configure Laravel with Inertia
|
||||||
Start by creating a new Laravel project with Inertia and Vue using the Laravel installer `laravel new my-app`:
|
Start by creating a new Laravel project with Inertia and Vue using the Laravel installer `laravel new my-app`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
laravel new my-app --typescript --breeze --stack=vue --git --no-interaction
|
laravel new my-app --breeze --stack=vue --git
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run the CLI
|
### Run the CLI
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,12 @@ description: Install and configure Nuxt.
|
||||||
|
|
||||||
Start by creating a new Nuxt project using `create-nuxt-app`:
|
Start by creating a new Nuxt project using `create-nuxt-app`:
|
||||||
|
|
||||||
|
<Callout>
|
||||||
|
|
||||||
|
If you're using the JS template, `jsconfig.json` must exist for the CLI to run without errors.
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx nuxi@latest init my-app
|
npx nuxi@latest init my-app
|
||||||
```
|
```
|
||||||
|
|
@ -82,7 +88,8 @@ export default defineNuxtModule<ShadcnVueOptions>({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async setup({ componentDir, prefix }) {
|
async setup({ componentDir, prefix }) {
|
||||||
const isVeeValidateExist = await tryResolveModule('vee-validate');
|
const veeValidate = await tryResolveModule('vee-validate');
|
||||||
|
const vaulVue = await tryResolveModule('vaul-vue');
|
||||||
|
|
||||||
addComponentsDir(
|
addComponentsDir(
|
||||||
{
|
{
|
||||||
|
|
@ -96,7 +103,7 @@ export default defineNuxtModule<ShadcnVueOptions>({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isVeeValidateExist !== undefined) {
|
if (veeValidate !== undefined) {
|
||||||
addComponent({
|
addComponent({
|
||||||
filePath: 'vee-validate',
|
filePath: 'vee-validate',
|
||||||
export: 'Form',
|
export: 'Form',
|
||||||
|
|
@ -112,6 +119,17 @@ export default defineNuxtModule<ShadcnVueOptions>({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(vaulVue !== undefined) {
|
||||||
|
['DrawerPortal', 'DrawerTrigger', 'DrawerClose'].forEach((item) => {
|
||||||
|
addComponent({
|
||||||
|
filePath: 'vaul-vue',
|
||||||
|
export: item,
|
||||||
|
name: prefix + item,
|
||||||
|
priority: 999,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
addComponent({
|
addComponent({
|
||||||
filePath: 'radix-vue',
|
filePath: 'radix-vue',
|
||||||
export: 'PaginationRoot',
|
export: 'PaginationRoot',
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,12 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
|
||||||
#### `vite.config`
|
#### `vite.config`
|
||||||
|
|
||||||
```typescript {5,6,9-13}
|
```typescript {5,6,9-13}
|
||||||
import path from "path"
|
import path from 'node:path'
|
||||||
import { defineConfig } from "vite"
|
import vue from '@vitejs/plugin-vue'
|
||||||
import vue from "@vitejs/plugin-vue"
|
import autoprefixer from 'autoprefixer'
|
||||||
|
|
||||||
import tailwind from "tailwindcss"
|
import tailwind from 'tailwindcss'
|
||||||
import autoprefixer from "autoprefixer"
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
css: {
|
css: {
|
||||||
|
|
@ -61,7 +61,7 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
'@': path.resolve(__dirname, './src'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -91,6 +91,12 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
|
||||||
|
|
||||||
### Edit tsconfig/jsconfig.json
|
### Edit tsconfig/jsconfig.json
|
||||||
|
|
||||||
|
<Callout>
|
||||||
|
|
||||||
|
If you're using TypeScript, the current version of Vite splits configuration into three files, requiring the same change for `tsconfig.app.json`.
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
Add the code below to the compilerOptions of your `tsconfig.json` or `jsconfig.json` so your app can resolve paths without error
|
Add the code below to the compilerOptions of your `tsconfig.json` or `jsconfig.json` so your app can resolve paths without error
|
||||||
|
|
||||||
```json {4-7}
|
```json {4-7}
|
||||||
|
|
@ -116,12 +122,12 @@ npm i -D @types/node
|
||||||
```
|
```
|
||||||
|
|
||||||
```typescript {15-19}
|
```typescript {15-19}
|
||||||
import path from "path"
|
import path from 'node:path'
|
||||||
import vue from "@vitejs/plugin-vue"
|
import vue from '@vitejs/plugin-vue'
|
||||||
import { defineConfig } from "vite"
|
import autoprefixer from 'autoprefixer'
|
||||||
|
|
||||||
import tailwind from "tailwindcss"
|
import tailwind from 'tailwindcss'
|
||||||
import autoprefixer from "autoprefixer"
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
css: {
|
css: {
|
||||||
|
|
@ -132,7 +138,7 @@ export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
'@': path.resolve(__dirname, './src'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -160,7 +166,7 @@ Which framework are you using? Vite / Nuxt / Laravel
|
||||||
Which style would you like to use? › Default
|
Which style would you like to use? › Default
|
||||||
Which color would you like to use as base color? › Slate
|
Which color would you like to use as base color? › Slate
|
||||||
Where is your tsconfig.json or jsconfig.json file? › ./tsconfig.json
|
Where is your tsconfig.json or jsconfig.json file? › ./tsconfig.json
|
||||||
Where is your global CSS file? › › src/index.css
|
Where is your global CSS file? › › src/assets/index.css
|
||||||
Do you want to use CSS variables for colors? › no / yes
|
Do you want to use CSS variables for colors? › no / yes
|
||||||
Where is your tailwind.config.js located? › tailwind.config.js
|
Where is your tailwind.config.js located? › tailwind.config.js
|
||||||
Configure the import alias for components: › @/components
|
Configure the import alias for components: › @/components
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ title: Introduction
|
||||||
description: Re-usable components built with Radix Vue, and Tailwind CSS.
|
description: Re-usable components built with Radix Vue, and Tailwind CSS.
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup >
|
<script setup>
|
||||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/new-york/ui/accordion'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
An unofficial, community-led [Vue](https://vuejs.org/) port of [shadcn/ui](https://ui.shadcn.com). We are not affiliated with [shadcn](https://twitter.com/shadcn), but we did get his blessing before creating a Vue version of his work. This project was born out of the need for a similar project for the Vue ecosystem.
|
An unofficial, community-led [Vue](https://vuejs.org/) port of [shadcn/ui](https://ui.shadcn.com). We are not affiliated with [shadcn](https://twitter.com/shadcn), but we did get his blessing before creating a Vue version of his work. This project was born out of the need for a similar project for the Vue ecosystem.
|
||||||
|
|
@ -19,8 +19,13 @@ Pick the components you need. Use the CLI to automatically add the components, o
|
||||||
|
|
||||||
_Use this as a reference to build your own component libraries._
|
_Use this as a reference to build your own component libraries._
|
||||||
|
|
||||||
|
<div class="[&>h2]:!mb-0">
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="[&_h3]:!mt-0">
|
||||||
<Accordion type="multiple">
|
<Accordion type="multiple">
|
||||||
|
|
||||||
<AccordionItem value="faq-1">
|
<AccordionItem value="faq-1">
|
||||||
|
|
@ -58,3 +63,4 @@ But let us know if you do use it. We'd love to see what you build with it.
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { Label } from '@/lib/registry/new-york/ui/label'
|
||||||
<Label for="date" class="shrink-0">
|
<Label for="date" class="shrink-0">
|
||||||
Pick a date
|
Pick a date
|
||||||
</Label>
|
</Label>
|
||||||
<DatePickerWithRange class="[&>button]:w-[260px]" />
|
<DatePickerWithRange />
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
||||||
<Select default-value="2">
|
<Select default-value="2">
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
id="security-level"
|
id="security-level"
|
||||||
class="line-clamp-1 w-full truncate"
|
class="w-full truncate"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder="Select level" />
|
<SelectValue placeholder="Select level" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import ChevronDownIcon from '~icons/radix-icons/chevron-down'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
AvatarImage,
|
AvatarImage,
|
||||||
} from '@/lib/registry/new-york/ui/avatar'
|
} from '@/lib/registry/new-york/ui/avatar'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
@ -16,12 +14,14 @@ import {
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/lib/registry/new-york/ui/card'
|
} from '@/lib/registry/new-york/ui/card'
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/new-york/ui/command'
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/new-york/ui/command'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import ChevronDownIcon from '~icons/radix-icons/chevron-down'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const sofiaRole = ref('Owner')
|
const sofiaRole = ref('Owner')
|
||||||
const jacksonRole = ref('Member')
|
const jacksonRole = ref('Member')
|
||||||
|
|
@ -64,25 +64,25 @@ const jacksonRole = ref('Member')
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No roles found.</CommandEmpty>
|
<CommandEmpty>No roles found.</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
<CommandItem value="Viewer" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Viewer" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Viewer</p>
|
<p>Viewer</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Can view and comment.
|
Can view and comment.
|
||||||
</p>
|
</p>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem value="Developer" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Developer" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Developer</p>
|
<p>Developer</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Can view, comment and edit.
|
Can view, comment and edit.
|
||||||
</p>
|
</p>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem value="Billing" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Billing" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Billing</p>
|
<p>Billing</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Can view, comment and manage billing.
|
Can view, comment and manage billing.
|
||||||
</p>
|
</p>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem value="Owner" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Owner" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Owner</p>
|
<p>Owner</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Admin-level access to all resources.
|
Admin-level access to all resources.
|
||||||
|
|
@ -122,25 +122,25 @@ const jacksonRole = ref('Member')
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No roles found.</CommandEmpty>
|
<CommandEmpty>No roles found.</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
<CommandItem value="Viewer" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Viewer" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Viewer</p>
|
<p>Viewer</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Can view and comment.
|
Can view and comment.
|
||||||
</p>
|
</p>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem value="Developer" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Developer" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Developer</p>
|
<p>Developer</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Can view, comment and edit.
|
Can view, comment and edit.
|
||||||
</p>
|
</p>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem value="Billing" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Billing" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Billing</p>
|
<p>Billing</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Can view, comment and manage billing.
|
Can view, comment and manage billing.
|
||||||
</p>
|
</p>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem value="Owner" class="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
<CommandItem value="Owner" class="space-y-1 flex flex-col items-start px-4 py-2">
|
||||||
<p>Owner</p>
|
<p>Owner</p>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
Admin-level access to all resources.
|
Admin-level access to all resources.
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<script setup lang="ts">
|
<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 { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
|
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
|
||||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
|
||||||
import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group'
|
|
||||||
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
|
import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group'
|
||||||
|
|
||||||
|
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||||
|
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
||||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
const notificationsFormSchema = toTypedSchema(z.object({
|
const notificationsFormSchema = toTypedSchema(z.object({
|
||||||
type: z.enum(['all', 'mentions', 'none'], {
|
type: z.enum(['all', 'mentions', 'none'], {
|
||||||
|
|
@ -33,7 +33,7 @@ const { handleSubmit } = useForm({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values, { resetForm }) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
toast({
|
toast({
|
||||||
title: 'You submitted the following values:',
|
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))),
|
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))),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, type Ref, computed } from 'vue'
|
|
||||||
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'radix-vue'
|
|
||||||
import { createDecade, createYear, toDate } from 'radix-vue/date'
|
|
||||||
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
|
||||||
import { useVModel } from '@vueuse/core'
|
|
||||||
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/default/ui/calendar'
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/default/ui/calendar'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
|
@ -13,6 +8,11 @@ import {
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/lib/registry/default/ui/select'
|
} from '@/lib/registry/default/ui/select'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { createDecade, createYear, toDate } from 'radix-vue/date'
|
||||||
|
import { computed, type HTMLAttributes, type Ref } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
|
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||||
modelValue: undefined,
|
modelValue: undefined,
|
||||||
|
|
@ -72,7 +72,7 @@ const formatter = useDateFormatter('en')
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
:default-value="props.placeholder.year.toString()"
|
:default-value="placeholder.year.toString()"
|
||||||
@update:model-value="(v) => {
|
@update:model-value="(v) => {
|
||||||
if (!v || !placeholder) return;
|
if (!v || !placeholder) return;
|
||||||
if (Number(v) === placeholder?.year) return;
|
if (Number(v) === placeholder?.year) return;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
|
|
||||||
import { Card, CardContent } from '@/lib/registry/default/ui/card'
|
import { Card, CardContent } from '@/lib/registry/default/ui/card'
|
||||||
|
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Carousel class="relative w-full max-w-xs">
|
<Carousel v-slot="{ canScrollNext }" class="relative w-full max-w-xs">
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
<CarouselItem v-for="(_, index) in 5" :key="index">
|
<CarouselItem v-for="(_, index) in 5" :key="index">
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
|
|
@ -17,6 +17,6 @@ import { Card, CardContent } from '@/lib/registry/default/ui/card'
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
<CarouselPrevious />
|
<CarouselPrevious />
|
||||||
<CarouselNext />
|
<CarouselNext v-if="canScrollNext" />
|
||||||
</Carousel>
|
</Carousel>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import {
|
|
||||||
ArrowUpCircle,
|
|
||||||
CheckCircle2,
|
|
||||||
Circle,
|
|
||||||
HelpCircle,
|
|
||||||
XCircle,
|
|
||||||
} from 'lucide-vue-next'
|
|
||||||
import type { Icon } from 'lucide-vue-next'
|
import type { Icon } from 'lucide-vue-next'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
|
|
@ -19,11 +9,21 @@ import {
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from '@/lib/registry/default/ui/command'
|
} from '@/lib/registry/default/ui/command'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
ArrowUpCircle,
|
||||||
|
CheckCircle2,
|
||||||
|
Circle,
|
||||||
|
HelpCircle,
|
||||||
|
XCircle,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
interface Status {
|
interface Status {
|
||||||
value: string
|
value: string
|
||||||
|
|
@ -60,7 +60,7 @@ const statuses: Status[] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
const value = ref<typeof statuses[number]>()
|
// const value = ref<typeof statuses[number]>()
|
||||||
|
|
||||||
const selectedStatus = ref<Status>()
|
const selectedStatus = ref<Status>()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {
|
import type {
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
} from '@tanstack/vue-table'
|
} from '@tanstack/vue-table'
|
||||||
import {
|
|
||||||
FlexRender,
|
|
||||||
createColumnHelper,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
|
|
||||||
useVueTable,
|
|
||||||
} from '@tanstack/vue-table'
|
|
||||||
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { h, ref } from 'vue'
|
|
||||||
import DropdownAction from './DataTableDemoColumn.vue'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
|
|
@ -36,6 +24,19 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/lib/registry/default/ui/table'
|
} from '@/lib/registry/default/ui/table'
|
||||||
import { cn, valueUpdater } from '@/lib/utils'
|
import { cn, valueUpdater } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
createColumnHelper,
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
import DropdownAction from './DataTableDemoColumn.vue'
|
||||||
|
|
||||||
export interface Payment {
|
export interface Payment {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -133,6 +134,7 @@ const columns = [
|
||||||
|
|
||||||
return h('div', { class: 'relative' }, h(DropdownAction, {
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
payment,
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -142,6 +144,7 @@ const sorting = ref<SortingState>([])
|
||||||
const columnFilters = ref<ColumnFiltersState>([])
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
const columnVisibility = ref<VisibilityState>({})
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
const rowSelection = ref({})
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
data,
|
data,
|
||||||
|
|
@ -150,15 +153,18 @@ const table = useVueTable({
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
state: {
|
state: {
|
||||||
get sorting() { return sorting.value },
|
get sorting() { return sorting.value },
|
||||||
get columnFilters() { return columnFilters.value },
|
get columnFilters() { return columnFilters.value },
|
||||||
get columnVisibility() { return columnVisibility.value },
|
get columnVisibility() { return columnVisibility.value },
|
||||||
get rowSelection() { return rowSelection.value },
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
columnPinning: {
|
columnPinning: {
|
||||||
left: ['status'],
|
left: ['status'],
|
||||||
},
|
},
|
||||||
|
|
@ -213,21 +219,24 @@ const table = useVueTable({
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<template v-if="table.getRowModel().rows?.length">
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
<TableRow
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
v-for="row in table.getRowModel().rows"
|
<TableRow :data-state="row.getIsSelected() && 'selected'">
|
||||||
:key="row.id"
|
<TableCell
|
||||||
:data-state="row.getIsSelected() && 'selected'"
|
v-for="cell in row.getVisibleCells()" :key="cell.id" :data-pinned="cell.column.getIsPinned()"
|
||||||
>
|
:class="cn(
|
||||||
<TableCell
|
{ 'sticky bg-background/95': cell.column.getIsPinned() },
|
||||||
v-for="cell in row.getVisibleCells()" :key="cell.id" :data-pinned="cell.column.getIsPinned()"
|
cell.column.getIsPinned() === 'left' ? 'left-0' : 'right-0',
|
||||||
:class="cn(
|
)"
|
||||||
{ 'sticky bg-background/95': cell.column.getIsPinned() },
|
>
|
||||||
cell.column.getIsPinned() === 'left' ? 'left-0' : 'right-0',
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
)"
|
</TableCell>
|
||||||
>
|
</TableRow>
|
||||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
</TableCell>
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
</TableRow>
|
{{ row.original }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<TableRow v-else>
|
<TableRow v-else>
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,13 @@
|
||||||
import type {
|
import type {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
} from '@tanstack/vue-table'
|
} from '@tanstack/vue-table'
|
||||||
import {
|
|
||||||
FlexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useVueTable,
|
|
||||||
} from '@tanstack/vue-table'
|
|
||||||
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { h, ref } from 'vue'
|
|
||||||
import DropdownAction from './DataTableDemoColumn.vue'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
|
|
@ -35,6 +25,18 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/lib/registry/default/ui/table'
|
} from '@/lib/registry/default/ui/table'
|
||||||
import { valueUpdater } from '@/lib/utils'
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
import DropdownAction from './DataTableDemoColumn.vue'
|
||||||
|
|
||||||
export interface Payment {
|
export interface Payment {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -130,6 +132,7 @@ const columns: ColumnDef<Payment>[] = [
|
||||||
|
|
||||||
return h('div', { class: 'relative' }, h(DropdownAction, {
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
payment,
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -139,6 +142,7 @@ const sorting = ref<SortingState>([])
|
||||||
const columnFilters = ref<ColumnFiltersState>([])
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
const columnVisibility = ref<VisibilityState>({})
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
const rowSelection = ref({})
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
data,
|
data,
|
||||||
|
|
@ -147,15 +151,18 @@ const table = useVueTable({
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
state: {
|
state: {
|
||||||
get sorting() { return sorting.value },
|
get sorting() { return sorting.value },
|
||||||
get columnFilters() { return columnFilters.value },
|
get columnFilters() { return columnFilters.value },
|
||||||
get columnVisibility() { return columnVisibility.value },
|
get columnVisibility() { return columnVisibility.value },
|
||||||
get rowSelection() { return rowSelection.value },
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -201,15 +208,18 @@ const table = useVueTable({
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<template v-if="table.getRowModel().rows?.length">
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
<TableRow
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
v-for="row in table.getRowModel().rows"
|
<TableRow :data-state="row.getIsSelected() && 'selected'">
|
||||||
:key="row.id"
|
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
:data-state="row.getIsSelected() && 'selected'"
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
>
|
</TableCell>
|
||||||
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
</TableRow>
|
||||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
</TableCell>
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
</TableRow>
|
{{ JSON.stringify(row.original) }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<TableRow v-else>
|
<TableRow v-else>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MoreHorizontal } from 'lucide-vue-next'
|
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/lib/registry/default/ui/dropdown-menu'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/lib/registry/default/ui/dropdown-menu'
|
||||||
|
import { MoreHorizontal } from 'lucide-vue-next'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
payment: {
|
payment: {
|
||||||
|
|
@ -9,6 +9,10 @@ defineProps<{
|
||||||
}
|
}
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'expand'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
function copy(id: string) {
|
function copy(id: string) {
|
||||||
navigator.clipboard.writeText(id)
|
navigator.clipboard.writeText(id)
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +31,9 @@ function copy(id: string) {
|
||||||
<DropdownMenuItem @click="copy(payment.id)">
|
<DropdownMenuItem @click="copy(payment.id)">
|
||||||
Copy payment ID
|
Copy payment ID
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem @click="$emit('expand')">
|
||||||
|
Expand
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>View customer</DropdownMenuItem>
|
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||||
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/dropdown-menu'
|
||||||
|
import { Input } from '@/lib/registry/default/ui/input'
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/lib/registry/default/ui/table'
|
||||||
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { h, ref, shallowRef } from 'vue'
|
||||||
|
import DropdownAction from './DataTableDemoColumn.vue'
|
||||||
|
|
||||||
|
export interface Payment {
|
||||||
|
id: string
|
||||||
|
amount: number
|
||||||
|
status: 'pending' | 'processing' | 'success' | 'failed'
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = shallowRef<Payment[]>([
|
||||||
|
{
|
||||||
|
id: 'm5gr84i9',
|
||||||
|
amount: 316,
|
||||||
|
status: 'success',
|
||||||
|
email: 'ken99@yahoo.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3u1reuv4',
|
||||||
|
amount: 242,
|
||||||
|
status: 'success',
|
||||||
|
email: 'Abe45@gmail.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'derv1ws0',
|
||||||
|
amount: 837,
|
||||||
|
status: 'processing',
|
||||||
|
email: 'Monserrat44@gmail.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5kma53ae',
|
||||||
|
amount: 874,
|
||||||
|
status: 'success',
|
||||||
|
email: 'Silas22@gmail.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bhqecj4p',
|
||||||
|
amount: 721,
|
||||||
|
status: 'failed',
|
||||||
|
email: 'carmella@hotmail.com',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const columns: ColumnDef<Payment>[] = [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => h(Checkbox, {
|
||||||
|
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||||
|
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||||
|
'ariaLabel': 'Select all',
|
||||||
|
}),
|
||||||
|
cell: ({ row }) => h(Checkbox, {
|
||||||
|
'checked': row.getIsSelected(),
|
||||||
|
'onUpdate:checked': value => row.toggleSelected(!!value),
|
||||||
|
'ariaLabel': 'Select row',
|
||||||
|
}),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
|
header: 'Status',
|
||||||
|
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('status')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'email',
|
||||||
|
header: ({ column }) => {
|
||||||
|
return h(Button, {
|
||||||
|
variant: 'ghost',
|
||||||
|
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
|
||||||
|
}, () => ['Email', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
|
||||||
|
},
|
||||||
|
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'amount',
|
||||||
|
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const amount = Number.parseFloat(row.getValue('amount'))
|
||||||
|
|
||||||
|
// Format the amount as a dollar amount
|
||||||
|
const formatted = new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'USD',
|
||||||
|
}).format(amount)
|
||||||
|
|
||||||
|
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const payment = row.original
|
||||||
|
|
||||||
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const sorting = ref<SortingState>([])
|
||||||
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
|
const table = useVueTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
|
state: {
|
||||||
|
get sorting() { return sorting.value },
|
||||||
|
get columnFilters() { return columnFilters.value },
|
||||||
|
get columnVisibility() { return columnVisibility.value },
|
||||||
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const statuses: Payment['status'][] = ['pending', 'processing', 'success', 'failed']
|
||||||
|
function randomize() {
|
||||||
|
data.value = data.value.map(item => ({
|
||||||
|
...item,
|
||||||
|
status: statuses[Math.floor(Math.random() * statuses.length)],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="flex gap-2 items-center py-4">
|
||||||
|
<Input
|
||||||
|
class="max-w-52"
|
||||||
|
placeholder="Filter emails..."
|
||||||
|
:model-value="table.getColumn('email')?.getFilterValue() as string"
|
||||||
|
@update:model-value=" table.getColumn('email')?.setFilterValue($event)"
|
||||||
|
/>
|
||||||
|
<Button @click="randomize">
|
||||||
|
Randomize
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button variant="outline" class="ml-auto">
|
||||||
|
Columns <ChevronDown class="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())"
|
||||||
|
:key="column.id"
|
||||||
|
class="capitalize"
|
||||||
|
:checked="column.getIsVisible()"
|
||||||
|
@update:checked="(value) => {
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ column.id }}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||||
|
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
||||||
|
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
|
<TableRow :data-state="row.getIsSelected() && 'selected'">
|
||||||
|
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
|
{{ JSON.stringify(row.original) }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<TableRow v-else>
|
||||||
|
<TableCell
|
||||||
|
:colspan="columns.length"
|
||||||
|
class="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end space-x-2 py-4">
|
||||||
|
<div class="flex-1 text-sm text-muted-foreground">
|
||||||
|
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
||||||
|
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div class="space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!table.getCanPreviousPage()"
|
||||||
|
@click="table.previousPage()"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!table.getCanNextPage()"
|
||||||
|
@click="table.nextPage()"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
79
apps/www/src/lib/registry/default/example/DialogForm.vue
Normal file
79
apps/www/src/lib/registry/default/example/DialogForm.vue
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/dialog'
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/default/ui/form'
|
||||||
|
|
||||||
|
import { Input } from '@/lib/registry/default/ui/input'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
username: z.string().min(2).max(50),
|
||||||
|
}))
|
||||||
|
|
||||||
|
function onSubmit(values: any) {
|
||||||
|
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 v-slot="{ submitForm }" as="" :validation-schema="formSchema" @submit="onSubmit">
|
||||||
|
<Dialog>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<form @submit="submitForm">
|
||||||
|
<FormField v-slot="{ componentField }" name="username">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="text" placeholder="shadcn" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
This is your public display name.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" form="dialogForm">
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ListItem from './NavigationMenuDemoItem.vue'
|
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuContent,
|
NavigationMenuContent,
|
||||||
|
|
@ -73,15 +72,46 @@ const components: { title: string, href: string, description: string }[] = [
|
||||||
</a>
|
</a>
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
</li>
|
</li>
|
||||||
<ListItem href="/docs" title="Introduction">
|
|
||||||
Re-usable components built using Radix UI and Tailwind CSS.
|
<li>
|
||||||
</ListItem>
|
<NavigationMenuLink as-child>
|
||||||
<ListItem href="/docs/installation" title="Installation">
|
<a
|
||||||
How to install dependencies and structure your app.
|
href="/docs"
|
||||||
</ListItem>
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
<ListItem href="/docs/primitives/typography" title="Typography">
|
>
|
||||||
Styles for headings, paragraphs, lists...etc
|
<div class="text-sm font-medium leading-none">Introduction</div>
|
||||||
</ListItem>
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
Re-usable components built using Radix UI and Tailwind CSS.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<a
|
||||||
|
href="/docs/installation"
|
||||||
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
|
>
|
||||||
|
<div class="text-sm font-medium leading-none">Installation</div>
|
||||||
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
How to install dependencies and structure your app.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<a
|
||||||
|
href="/docs/primitives/typography"
|
||||||
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
|
>
|
||||||
|
<div class="text-sm font-medium leading-none">Typography</div>
|
||||||
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
Styles for headings, paragraphs, lists...etc
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</NavigationMenuContent>
|
</NavigationMenuContent>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
|
@ -89,14 +119,19 @@ const components: { title: string, href: string, description: string }[] = [
|
||||||
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
|
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
|
||||||
<NavigationMenuContent>
|
<NavigationMenuContent>
|
||||||
<ul class="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
|
<ul class="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
|
||||||
<ListItem
|
<li v-for="component in components" :key="component.title">
|
||||||
v-for="component in components"
|
<NavigationMenuLink as-child>
|
||||||
:key="component.title"
|
<a
|
||||||
:title="component.title"
|
:href="component.href"
|
||||||
:href="component.href"
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
>
|
>
|
||||||
{{ component.description }}
|
<div class="text-sm font-medium leading-none">{{ component.title }}</div>
|
||||||
</ListItem>
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
{{ component.description }}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</NavigationMenuContent>
|
</NavigationMenuContent>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import {
|
|
||||||
NavigationMenuLink,
|
|
||||||
} from '@/lib/registry/default/ui/navigation-menu'
|
|
||||||
|
|
||||||
defineProps<{ title?: string, href?: string }>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<li>
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<a
|
|
||||||
:href="href"
|
|
||||||
:class="cn(
|
|
||||||
'block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
|
|
||||||
$attrs.class ?? '',
|
|
||||||
)"
|
|
||||||
>
|
|
||||||
<div class="text-sm font-medium leading-none">{{ title }}</div>
|
|
||||||
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
|
||||||
<slot />
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
<script setup lang="ts">
|
<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 { Button } from '@/lib/registry/default/ui/button'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -22,6 +17,11 @@ import {
|
||||||
} from '@/lib/registry/default/ui/number-field'
|
} from '@/lib/registry/default/ui/number-field'
|
||||||
import { toast } from '@/lib/registry/default/ui/toast'
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
payment: z.number().min(10, 'Min 10 euros to send payment').max(5000, 'Max 5000 euros to send payment'),
|
payment: z.number().min(10, 'Min 10 euros to send payment').max(5000, 'Max 5000 euros to send payment'),
|
||||||
}))
|
}))
|
||||||
|
|
@ -43,7 +43,7 @@ const onSubmit = handleSubmit((values) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||||
<FormField name="payment">
|
<FormField v-slot="{ value }" name="payment">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Payment</FormLabel>
|
<FormLabel>Payment</FormLabel>
|
||||||
<NumberField
|
<NumberField
|
||||||
|
|
@ -55,6 +55,7 @@ const onSubmit = handleSubmit((values) => {
|
||||||
currencyDisplay: 'code',
|
currencyDisplay: 'code',
|
||||||
currencySign: 'accounting',
|
currencySign: 'accounting',
|
||||||
}"
|
}"
|
||||||
|
:model-value="value"
|
||||||
@update:model-value="(v) => {
|
@update:model-value="(v) => {
|
||||||
if (v) {
|
if (v) {
|
||||||
setFieldValue('payment', v)
|
setFieldValue('payment', v)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,4 @@
|
||||||
<script setup lang="ts">
|
<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 {
|
|
||||||
PinInput,
|
|
||||||
PinInputGroup,
|
|
||||||
PinInputInput,
|
|
||||||
} from '@/lib/registry/default/ui/pin-input'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -17,7 +8,16 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
|
import {
|
||||||
|
PinInput,
|
||||||
|
PinInputGroup,
|
||||||
|
PinInputInput,
|
||||||
|
} from '@/lib/registry/default/ui/pin-input'
|
||||||
import { toast } from '@/lib/registry/default/ui/toast'
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
pin: z.array(z.coerce.string()).length(5, { message: 'Invalid input' }),
|
pin: z.array(z.coerce.string()).length(5, { message: 'Invalid input' }),
|
||||||
|
|
@ -48,7 +48,7 @@ const handleComplete = (e: string[]) => console.log(e.join(''))
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<PinInput
|
<PinInput
|
||||||
id="pin-input"
|
id="pin-input"
|
||||||
v-model="value!"
|
:model-value="value"
|
||||||
placeholder="○"
|
placeholder="○"
|
||||||
class="flex gap-2 items-center mt-1"
|
class="flex gap-2 items-center mt-1"
|
||||||
otp
|
otp
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { Separator } from '@/lib/registry/default/ui/separator'
|
||||||
An open-source UI component library.
|
An open-source UI component library.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator class="my-4" />
|
<Separator class="my-4" label="Or" />
|
||||||
<div class="flex h-5 items-center space-x-4 text-sm">
|
<div class="flex h-5 items-center space-x-4 text-sm">
|
||||||
<div>Blog</div>
|
<div>Blog</div>
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
|
|
|
||||||
56
apps/www/src/lib/registry/default/example/StepperDemo.vue
Normal file
56
apps/www/src/lib/registry/default/example/StepperDemo.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Stepper, StepperDescription, StepperIndicator, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/default/ui/stepper'
|
||||||
|
|
||||||
|
import { BookUser, Check, CreditCard, Truck } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const steps = [{
|
||||||
|
step: 1,
|
||||||
|
title: 'Address',
|
||||||
|
description: 'Add your address here',
|
||||||
|
icon: BookUser,
|
||||||
|
}, {
|
||||||
|
step: 2,
|
||||||
|
title: 'Shipping',
|
||||||
|
description: 'Set your preferred shipping method',
|
||||||
|
icon: Truck,
|
||||||
|
}, {
|
||||||
|
step: 3,
|
||||||
|
title: 'Payment',
|
||||||
|
description: 'Add any payment information you have',
|
||||||
|
icon: CreditCard,
|
||||||
|
}, {
|
||||||
|
step: 4,
|
||||||
|
title: 'Checkout',
|
||||||
|
description: 'Confirm your order',
|
||||||
|
icon: Check,
|
||||||
|
}]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper>
|
||||||
|
<StepperItem
|
||||||
|
v-for="item in steps"
|
||||||
|
:key="item.step"
|
||||||
|
class="basis-1/4"
|
||||||
|
:step="item.step"
|
||||||
|
>
|
||||||
|
<StepperTrigger>
|
||||||
|
<StepperIndicator>
|
||||||
|
<component :is="item.icon" class="w-4 h-4" />
|
||||||
|
</StepperIndicator>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<StepperTitle>
|
||||||
|
{{ item.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription>
|
||||||
|
{{ item.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperTrigger>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="item.step !== steps[steps.length - 1].step"
|
||||||
|
class="w-full h-px"
|
||||||
|
/>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
223
apps/www/src/lib/registry/default/example/StepperForm.vue
Normal file
223
apps/www/src/lib/registry/default/example/StepperForm.vue
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
|
||||||
|
import { Input } from '@/lib/registry/default/ui/input'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/lib/registry/default/ui/select'
|
||||||
|
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/default/ui/stepper'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { Check, Circle, Dot } from 'lucide-vue-next'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
const formSchema = [
|
||||||
|
z.object({
|
||||||
|
fullName: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
password: z.string().min(2).max(50),
|
||||||
|
confirmPassword: z.string(),
|
||||||
|
}).refine(
|
||||||
|
(values) => {
|
||||||
|
return values.password === values.confirmPassword
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Passwords must match!',
|
||||||
|
path: ['confirmPassword'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
z.object({
|
||||||
|
favoriteDrink: z.union([z.literal('coffee'), z.literal('tea'), z.literal('soda')]),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
const stepIndex = ref(1)
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
title: 'Your details',
|
||||||
|
description: 'Provide your name and email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
title: 'Your password',
|
||||||
|
description: 'Choose a password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 3,
|
||||||
|
title: 'Your Favorite Drink',
|
||||||
|
description: 'Choose a drink',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function onSubmit(values: any) {
|
||||||
|
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
|
||||||
|
v-slot="{ meta, values, validate }"
|
||||||
|
as="" keep-values :validation-schema="toTypedSchema(formSchema[stepIndex - 1])"
|
||||||
|
>
|
||||||
|
<Stepper v-slot="{ isNextDisabled, isPrevDisabled, nextStep, prevStep }" v-model="stepIndex" class="block w-full">
|
||||||
|
<form
|
||||||
|
@submit="(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
validate()
|
||||||
|
|
||||||
|
if (stepIndex === steps.length && meta.valid) {
|
||||||
|
onSubmit(values)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="flex w-full flex-start gap-2">
|
||||||
|
<StepperItem
|
||||||
|
v-for="step in steps"
|
||||||
|
:key="step.step"
|
||||||
|
v-slot="{ state }"
|
||||||
|
class="relative flex w-full flex-col items-center justify-center"
|
||||||
|
:step="step.step"
|
||||||
|
>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="step.step !== steps[steps.length - 1].step"
|
||||||
|
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StepperTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||||
|
size="icon"
|
||||||
|
class="z-10 rounded-full shrink-0"
|
||||||
|
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
|
||||||
|
:disabled="state !== 'completed' && !meta.valid"
|
||||||
|
>
|
||||||
|
<Check v-if="state === 'completed'" class="size-5" />
|
||||||
|
<Circle v-if="state === 'active'" />
|
||||||
|
<Dot v-if="state === 'inactive'" />
|
||||||
|
</Button>
|
||||||
|
</StepperTrigger>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-col items-center text-center">
|
||||||
|
<StepperTitle
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="text-sm font-semibold transition lg:text-base"
|
||||||
|
>
|
||||||
|
{{ step.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
|
||||||
|
>
|
||||||
|
{{ step.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 mt-4">
|
||||||
|
<template v-if="stepIndex === 1">
|
||||||
|
<FormField v-slot="{ componentField }" name="fullName">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Full Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="text" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="email">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="email " v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="stepIndex === 2">
|
||||||
|
<FormField v-slot="{ componentField }" name="password">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Password</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="password" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="confirmPassword">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Confirm Password</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="password" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="stepIndex === 3">
|
||||||
|
<FormField v-slot="{ componentField }" name="favoriteDrink">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Drink</FormLabel>
|
||||||
|
|
||||||
|
<Select v-bind="componentField">
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a drink" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="coffee">
|
||||||
|
Coffe
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="tea">
|
||||||
|
Tea
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="soda">
|
||||||
|
Soda
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<Button :disabled="isPrevDisabled" variant="outline" size="sm" @click="prevStep()">
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Button v-if="stepIndex !== 3" :type="meta.valid ? 'button' : 'submit'" :disabled="isNextDisabled" size="sm" @click="meta.valid && nextStep()">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="stepIndex === 3" size="sm" type="submit"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Stepper>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/default/ui/stepper'
|
||||||
|
import { Check, Circle, Dot } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
title: 'Your details',
|
||||||
|
description: 'Provide your name and email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
title: 'Company details',
|
||||||
|
description: 'A few details about your company',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 3,
|
||||||
|
title: 'Invite your team',
|
||||||
|
description: 'Start collaborating with your team',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper class="flex w-full items-start gap-2">
|
||||||
|
<StepperItem
|
||||||
|
v-for="step in steps"
|
||||||
|
:key="step.step"
|
||||||
|
v-slot="{ state }"
|
||||||
|
class="relative flex w-full flex-col items-center justify-center"
|
||||||
|
:step="step.step"
|
||||||
|
>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="step.step !== steps[steps.length - 1].step"
|
||||||
|
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StepperTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||||
|
size="icon"
|
||||||
|
class="z-10 rounded-full shrink-0"
|
||||||
|
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
|
||||||
|
>
|
||||||
|
<Check v-if="state === 'completed'" class="size-5" />
|
||||||
|
<Circle v-if="state === 'active'" />
|
||||||
|
<Dot v-if="state === 'inactive'" />
|
||||||
|
</Button>
|
||||||
|
</StepperTrigger>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-col items-center text-center">
|
||||||
|
<StepperTitle
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="text-sm font-semibold transition lg:text-base"
|
||||||
|
>
|
||||||
|
{{ step.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
|
||||||
|
>
|
||||||
|
{{ step.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/default/ui/stepper'
|
||||||
|
import { Check, Circle, Dot } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
title: 'Your details',
|
||||||
|
description:
|
||||||
|
'Provide your name and email address. We will use this information to create your account',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
title: 'Company details',
|
||||||
|
description: 'A few details about your company will help us personalize your experience',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 3,
|
||||||
|
title: 'Invite your team',
|
||||||
|
description:
|
||||||
|
'Start collaborating with your team by inviting them to join your account. You can skip this step and invite them later',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper orientation="vertical" class="mx-auto flex w-full max-w-md flex-col justify-start gap-10">
|
||||||
|
<StepperItem
|
||||||
|
v-for="step in steps"
|
||||||
|
:key="step.step"
|
||||||
|
v-slot="{ state }"
|
||||||
|
class="relative flex w-full items-start gap-6"
|
||||||
|
:step="step.step"
|
||||||
|
>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="step.step !== steps[steps.length - 1].step"
|
||||||
|
class="absolute left-[18px] top-[38px] block h-[105%] w-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StepperTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||||
|
size="icon"
|
||||||
|
class="z-10 rounded-full shrink-0"
|
||||||
|
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
|
||||||
|
>
|
||||||
|
<Check v-if="state === 'completed'" class="size-5" />
|
||||||
|
<Circle v-if="state === 'active'" />
|
||||||
|
<Dot v-if="state === 'inactive'" />
|
||||||
|
</Button>
|
||||||
|
</StepperTrigger>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<StepperTitle
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="text-sm font-semibold transition lg:text-base"
|
||||||
|
>
|
||||||
|
{{ step.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
|
||||||
|
>
|
||||||
|
{{ step.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/lib/registry/default/ui/card'
|
||||||
|
import { Input } from '@/lib/registry/default/ui/input'
|
||||||
|
import { Label } from '@/lib/registry/default/ui/label'
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/tabs'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Tabs default-value="account" class="w-[400px]" orientation="vertical">
|
||||||
|
<TabsList class="grid w-full grid-cols-1">
|
||||||
|
<TabsTrigger value="account">
|
||||||
|
Accounts
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="password">
|
||||||
|
Password
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="account">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Account</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Make changes to your account here. Click save when you're done.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent class="space-y-2">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="name">Name</Label>
|
||||||
|
<Input id="name" default-value="Pedro Duarte" />
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="username">Username</Label>
|
||||||
|
<Input id="username" default-value="@peduarte" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Save changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="password">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Password</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Change your password here. After saving, you'll be logged out.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent class="space-y-2">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="current">Current password</Label>
|
||||||
|
<Input id="current" type="password" />
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="new">New password</Label>
|
||||||
|
<Input id="new" type="password" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Save password</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</template>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import { ComboboxAnchor, ComboboxInput, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
|
||||||
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/lib/registry/default/ui/command'
|
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/lib/registry/default/ui/command'
|
||||||
import { TagsInput, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from '@/lib/registry/default/ui/tags-input'
|
import { TagsInput, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from '@/lib/registry/default/ui/tags-input'
|
||||||
|
import { ComboboxAnchor, ComboboxContent, ComboboxInput, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const frameworks = [
|
const frameworks = [
|
||||||
{ value: 'next.js', label: 'Next.js' },
|
{ value: 'next.js', label: 'Next.js' },
|
||||||
|
|
@ -28,7 +28,7 @@ const filteredFrameworks = computed(() => frameworks.filter(i => !modelValue.val
|
||||||
</TagsInputItem>
|
</TagsInputItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ComboboxRoot v-model="modelValue" v-model:open="open" v-model:searchTerm="searchTerm" class="w-full">
|
<ComboboxRoot v-model="modelValue" v-model:open="open" v-model:search-term="searchTerm" class="w-full">
|
||||||
<ComboboxAnchor as-child>
|
<ComboboxAnchor as-child>
|
||||||
<ComboboxInput placeholder="Framework..." as-child>
|
<ComboboxInput placeholder="Framework..." as-child>
|
||||||
<TagsInputInput class="w-full px-3" :class="modelValue.length > 0 ? 'mt-2' : ''" @keydown.enter.prevent />
|
<TagsInputInput class="w-full px-3" :class="modelValue.length > 0 ? 'mt-2' : ''" @keydown.enter.prevent />
|
||||||
|
|
@ -36,29 +36,31 @@ const filteredFrameworks = computed(() => frameworks.filter(i => !modelValue.val
|
||||||
</ComboboxAnchor>
|
</ComboboxAnchor>
|
||||||
|
|
||||||
<ComboboxPortal>
|
<ComboboxPortal>
|
||||||
<CommandList
|
<ComboboxContent>
|
||||||
position="popper"
|
<CommandList
|
||||||
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
position="popper"
|
||||||
>
|
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
||||||
<CommandEmpty />
|
>
|
||||||
<CommandGroup>
|
<CommandEmpty />
|
||||||
<CommandItem
|
<CommandGroup>
|
||||||
v-for="framework in filteredFrameworks" :key="framework.value" :value="framework.label"
|
<CommandItem
|
||||||
@select.prevent="(ev) => {
|
v-for="framework in filteredFrameworks" :key="framework.value" :value="framework.label"
|
||||||
if (typeof ev.detail.value === 'string') {
|
@select.prevent="(ev) => {
|
||||||
searchTerm = ''
|
if (typeof ev.detail.value === 'string') {
|
||||||
modelValue.push(ev.detail.value)
|
searchTerm = ''
|
||||||
}
|
modelValue.push(ev.detail.value)
|
||||||
|
}
|
||||||
|
|
||||||
if (filteredFrameworks.length === 0) {
|
if (filteredFrameworks.length === 0) {
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ framework.label }}
|
{{ framework.label }}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
|
</ComboboxContent>
|
||||||
</ComboboxPortal>
|
</ComboboxPortal>
|
||||||
</ComboboxRoot>
|
</ComboboxRoot>
|
||||||
</TagsInput>
|
</TagsInput>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/default/ui/form'
|
||||||
|
import { TagsInput, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from '@/lib/registry/default/ui/tags-input'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
fruits: z.array(z.string()).min(1).max(3),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { handleSubmit } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
initialValues: {
|
||||||
|
fruits: ['Apple', 'Banana'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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="{ value }" name="fruits">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Fruits</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<TagsInput :model-value="value">
|
||||||
|
<TagsInputItem v-for="item in value" :key="item" :value="item">
|
||||||
|
<TagsInputItemText />
|
||||||
|
<TagsInputItemDelete />
|
||||||
|
</TagsInputItem>
|
||||||
|
|
||||||
|
<TagsInputInput placeholder="Fruits..." />
|
||||||
|
</TagsInput>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Select your favorite fruits.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts" generic="T extends ZodObjectOrWrapped">
|
<script setup lang="ts" generic="T extends ZodObjectOrWrapped">
|
||||||
import { computed, toRefs } from 'vue'
|
|
||||||
import type { ZodAny, z } from 'zod'
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
|
||||||
import type { FormContext, GenericObject } from 'vee-validate'
|
import type { FormContext, GenericObject } from 'vee-validate'
|
||||||
import { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils'
|
import type { z, ZodAny } from 'zod'
|
||||||
import type { Config, ConfigItem, Dependency, Shape } from './interface'
|
import type { Config, ConfigItem, Dependency, Shape } from './interface'
|
||||||
|
import { Form } from '@/lib/registry/default/ui/form'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { computed, toRefs } from 'vue'
|
||||||
import AutoFormField from './AutoFormField.vue'
|
import AutoFormField from './AutoFormField.vue'
|
||||||
import { provideDependencies } from './dependencies'
|
import { provideDependencies } from './dependencies'
|
||||||
import { Form } from '@/lib/registry/default/ui/form'
|
import { getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema, type ZodObjectOrWrapped } from './utils'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
schema: T
|
schema: T
|
||||||
|
|
@ -17,7 +17,7 @@ const props = defineProps<{
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
submit: [event: GenericObject]
|
submit: [event: z.infer<T>]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { dependencies } = toRefs(props)
|
const { dependencies } = toRefs(props)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CalendarCell
|
<CalendarCell
|
||||||
:class="cn('relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50', props.class)"
|
:class="cn('relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-view])]:bg-accent/50', props.class)"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
// Unavailable
|
// Unavailable
|
||||||
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
||||||
// Outside months
|
// Outside months
|
||||||
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
|
'data-[outside-view]:text-muted-foreground data-[outside-view]:opacity-50 [&[data-outside-view][data-selected]]:bg-accent/50 [&[data-outside-view][data-selected]]:text-muted-foreground [&[data-outside-view][data-selected]]:opacity-30',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useProvideCarousel } from './useCarousel'
|
|
||||||
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'
|
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useProvideCarousel } from './useCarousel'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
|
@ -9,9 +9,17 @@ const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
||||||
|
|
||||||
const emits = defineEmits<CarouselEmits>()
|
const emits = defineEmits<CarouselEmits>()
|
||||||
|
|
||||||
const carouselArgs = useProvideCarousel(props, emits)
|
const { canScrollNext, canScrollPrev, carouselApi, carouselRef, orientation, scrollNext, scrollPrev } = useProvideCarousel(props, emits)
|
||||||
|
|
||||||
defineExpose(carouselArgs)
|
defineExpose({
|
||||||
|
canScrollNext,
|
||||||
|
canScrollPrev,
|
||||||
|
carouselApi,
|
||||||
|
carouselRef,
|
||||||
|
orientation,
|
||||||
|
scrollNext,
|
||||||
|
scrollPrev,
|
||||||
|
})
|
||||||
|
|
||||||
function onKeyDown(event: KeyboardEvent) {
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
|
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
|
||||||
|
|
@ -19,14 +27,14 @@ function onKeyDown(event: KeyboardEvent) {
|
||||||
|
|
||||||
if (event.key === prevKey) {
|
if (event.key === prevKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
carouselArgs.scrollPrev()
|
scrollPrev()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === nextKey) {
|
if (event.key === nextKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
carouselArgs.scrollNext()
|
scrollNext()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -39,6 +47,6 @@ function onKeyDown(event: KeyboardEvent) {
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keydown="onKeyDown"
|
@keydown="onKeyDown"
|
||||||
>
|
>
|
||||||
<slot v-bind="carouselArgs" />
|
<slot :can-scroll-next :can-scroll-prev :carousel-api :carousel-ref :orientation :scroll-next :scroll-prev />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
<script setup lang="ts" generic="T extends Record<string, any>">
|
<script setup lang="ts" generic="T extends Record<string, any>">
|
||||||
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
|
|
||||||
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
|
|
||||||
import { Area, Axis, Line } from '@unovis/ts'
|
|
||||||
import { type Component, computed, ref } from 'vue'
|
|
||||||
import { useMounted } from '@vueuse/core'
|
|
||||||
import type { BaseChartProps } from '.'
|
import type { BaseChartProps } from '.'
|
||||||
import { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'
|
import { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
|
||||||
|
import { Area, Axis, Line } from '@unovis/ts'
|
||||||
|
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
|
||||||
|
import { useMounted } from '@vueuse/core'
|
||||||
|
import { useId } from 'radix-vue'
|
||||||
|
import { type Component, computed, ref } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<BaseChartProps<T> & {
|
const props = withDefaults(defineProps<BaseChartProps<T> & {
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,6 +42,8 @@ const emits = defineEmits<{
|
||||||
type KeyOfT = Extract<keyof T, string>
|
type KeyOfT = Extract<keyof T, string>
|
||||||
type Data = typeof props.data[number]
|
type Data = typeof props.data[number]
|
||||||
|
|
||||||
|
const chartRef = useId()
|
||||||
|
|
||||||
const index = computed(() => props.index as KeyOfT)
|
const index = computed(() => props.index as KeyOfT)
|
||||||
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
|
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
|
||||||
|
|
||||||
|
|
@ -64,7 +67,7 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
|
||||||
<VisXYContainer :style="{ height: isMounted ? '100%' : 'auto' }" :margin="{ left: 20, right: 20 }" :data="data">
|
<VisXYContainer :style="{ height: isMounted ? '100%' : 'auto' }" :margin="{ left: 20, right: 20 }" :data="data">
|
||||||
<svg width="0" height="0">
|
<svg width="0" height="0">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient v-for="(color, i) in colors" :id="`color-${i}`" :key="i" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient v-for="(color, i) in colors" :id="`${chartRef}-color-${i}`" :key="i" x1="0" y1="0" x2="0" y2="1">
|
||||||
<template v-if="showGradiant">
|
<template v-if="showGradiant">
|
||||||
<stop offset="5%" :stop-color="color" stop-opacity="0.4" />
|
<stop offset="5%" :stop-color="color" stop-opacity="0.4" />
|
||||||
<stop offset="95%" :stop-color="color" stop-opacity="0" />
|
<stop offset="95%" :stop-color="color" stop-opacity="0" />
|
||||||
|
|
@ -86,7 +89,7 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
|
||||||
:curve-type="curveType"
|
:curve-type="curveType"
|
||||||
:attributes="{
|
:attributes="{
|
||||||
[Area.selectors.area]: {
|
[Area.selectors.area]: {
|
||||||
fill: `url(#color-${i})`,
|
fill: `url(#${chartRef}-color-${i})`,
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
:opacity="legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1"
|
:opacity="legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1"
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,19 @@ export function useFormField() {
|
||||||
const fieldContext = inject(FieldContextKey)
|
const fieldContext = inject(FieldContextKey)
|
||||||
const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY)
|
const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY)
|
||||||
|
|
||||||
const fieldState = {
|
|
||||||
valid: useIsFieldValid(),
|
|
||||||
isDirty: useIsFieldDirty(),
|
|
||||||
isTouched: useIsFieldTouched(),
|
|
||||||
error: useFieldError(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fieldContext)
|
if (!fieldContext)
|
||||||
throw new Error('useFormField should be used within <FormField>')
|
throw new Error('useFormField should be used within <FormField>')
|
||||||
|
|
||||||
const { name } = fieldContext
|
const { name } = fieldContext
|
||||||
const id = fieldItemContext
|
const id = fieldItemContext
|
||||||
|
|
||||||
|
const fieldState = {
|
||||||
|
valid: useIsFieldValid(name),
|
||||||
|
isDirty: useIsFieldDirty(name),
|
||||||
|
isTouched: useIsFieldTouched(name),
|
||||||
|
error: useFieldError(name),
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const props = defineProps<{
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="cn('relative', props.class)">
|
<div :class="cn('relative [&>[data-slot=input]]:has-[[data-slot=increment]]:pr-5 [&>[data-slot=input]]:has-[[data-slot=decrement]]:pl-5', props.class)">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NumberFieldDecrementProps } from 'radix-vue'
|
import type { NumberFieldDecrementProps } from 'radix-vue'
|
||||||
import { NumberFieldDecrement, useForwardProps } from 'radix-vue'
|
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { Minus } from 'lucide-vue-next'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Minus } from 'lucide-vue-next'
|
||||||
|
import { NumberFieldDecrement, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<NumberFieldDecrementProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<NumberFieldDecrementProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ const forwarded = useForwardProps(delegatedProps)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NumberFieldDecrement v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 left-0 p-3 disabled:cursor-not-allowed disabled:opacity-20', props.class)">
|
<NumberFieldDecrement data-slot="decrement" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 left-0 p-3 disabled:cursor-not-allowed disabled:opacity-20', props.class)">
|
||||||
<slot>
|
<slot>
|
||||||
<Minus class="h-4 w-4" />
|
<Minus class="h-4 w-4" />
|
||||||
</slot>
|
</slot>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NumberFieldIncrementProps } from 'radix-vue'
|
import type { NumberFieldIncrementProps } from 'radix-vue'
|
||||||
import { NumberFieldIncrement, useForwardProps } from 'radix-vue'
|
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { Plus } from 'lucide-vue-next'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Plus } from 'lucide-vue-next'
|
||||||
|
import { NumberFieldIncrement, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<NumberFieldIncrementProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<NumberFieldIncrementProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ const forwarded = useForwardProps(delegatedProps)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NumberFieldIncrement v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 right-0 disabled:cursor-not-allowed disabled:opacity-20 p-3', props.class)">
|
<NumberFieldIncrement data-slot="increment" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 right-0 disabled:cursor-not-allowed disabled:opacity-20 p-3', props.class)">
|
||||||
<slot>
|
<slot>
|
||||||
<Plus class="h-4 w-4" />
|
<Plus class="h-4 w-4" />
|
||||||
</slot>
|
</slot>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NumberFieldInput } from 'radix-vue'
|
import type { HTMLAttributes } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { NumberFieldInput } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NumberFieldInput :class="cn('flex h-10 w-full rounded-md border border-input bg-background px-10 py-2 text-sm text-center ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50')" />
|
<NumberFieldInput
|
||||||
|
data-slot="input"
|
||||||
|
:class="cn('flex h-10 w-full rounded-md border border-input bg-background py-2 text-sm text-center ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { RangeCalendarCell, type RangeCalendarCellProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { RangeCalendarCell, type RangeCalendarCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<RangeCalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<RangeCalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RangeCalendarCell
|
<RangeCalendarCell
|
||||||
:class="cn('relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:bg-accent first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-outside-month])]:bg-accent/50 [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md', props.class)"
|
:class="cn('relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:bg-accent first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-outside-view])]:bg-accent/50 [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md', props.class)"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { RangeCalendarCellTrigger, type RangeCalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { RangeCalendarCellTrigger, type RangeCalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<RangeCalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<RangeCalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
// Selection End
|
// Selection End
|
||||||
'data-[selection-end]:bg-primary data-[selection-end]:text-primary-foreground data-[selection-end]:hover:bg-primary data-[selection-end]:hover:text-primary-foreground data-[selection-end]:focus:bg-primary data-[selection-end]:focus:text-primary-foreground',
|
'data-[selection-end]:bg-primary data-[selection-end]:text-primary-foreground data-[selection-end]:hover:bg-primary data-[selection-end]:hover:text-primary-foreground data-[selection-end]:focus:bg-primary data-[selection-end]:focus:text-primary-foreground',
|
||||||
// Outside months
|
// Outside months
|
||||||
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
|
'data-[outside-view]:text-muted-foreground data-[outside-view]:opacity-50 [&[data-outside-view][data-selected]]:bg-accent/50 [&[data-outside-view][data-selected]]:text-muted-foreground [&[data-outside-view][data-selected]]:opacity-30',
|
||||||
// Disabled
|
// Disabled
|
||||||
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
||||||
// Unavailable
|
// Unavailable
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { RangeCalendarHeadCell, type RangeCalendarHeadCellProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { RangeCalendarHeadCell, type RangeCalendarHeadCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<RangeCalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<RangeCalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RangeCalendarHeadCell :class="cn('w-8 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)" v-bind="forwardedProps">
|
<RangeCalendarHeadCell :class="cn('w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)" v-bind="forwardedProps">
|
||||||
<slot />
|
<slot />
|
||||||
</RangeCalendarHeadCell>
|
</RangeCalendarHeadCell>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { SelectIcon, SelectTrigger, type SelectTriggerProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { ChevronDown } from 'lucide-vue-next'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { SelectIcon, SelectTrigger, type SelectTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<SelectTriggerProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<SelectTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -19,13 +19,13 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<SelectIcon as-child>
|
<SelectIcon as-child>
|
||||||
<ChevronDown class="w-4 h-4 opacity-50" />
|
<ChevronDown class="w-4 h-4 opacity-50 shrink-0" />
|
||||||
</SelectIcon>
|
</SelectIcon>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { Separator, type SeparatorProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Separator, type SeparatorProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<
|
||||||
|
SeparatorProps & { class?: HTMLAttributes['class'], label?: string }
|
||||||
|
>()
|
||||||
|
|
||||||
const delegatedProps = computed(() => {
|
const delegatedProps = computed(() => {
|
||||||
const { class: _, ...delegated } = props
|
const { class: _, ...delegated } = props
|
||||||
|
|
@ -15,6 +17,19 @@ const delegatedProps = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<Separator
|
<Separator
|
||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
:class="cn('shrink-0 bg-border', props.orientation === 'vertical' ? 'w-px h-full' : 'h-px w-full', props.class)"
|
:class="
|
||||||
/>
|
cn(
|
||||||
|
'shrink-0 bg-border relative',
|
||||||
|
props.orientation === 'vertical' ? 'w-px h-full' : 'h-px w-full',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="props.label"
|
||||||
|
:class="cn('text-xs text-muted-foreground bg-background absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex justify-center items-center',
|
||||||
|
props.orientation === 'vertical' ? 'w-[1px] px-1 py-2' : 'h-[1px] py-1 px-2',
|
||||||
|
)"
|
||||||
|
>{{ props.label }}</span>
|
||||||
|
</Separator>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import type { SliderRootEmits, SliderRootProps } from 'radix-vue'
|
import type { SliderRootEmits, SliderRootProps } from 'radix-vue'
|
||||||
import { SliderRange, SliderRoot, SliderThumb, SliderTrack, useForwardPropsEmits } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { SliderRange, SliderRoot, SliderThumb, SliderTrack, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<SliderRootProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<SliderRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
const emits = defineEmits<SliderRootEmits>()
|
const emits = defineEmits<SliderRootEmits>()
|
||||||
|
|
@ -19,13 +19,13 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
<template>
|
<template>
|
||||||
<SliderRoot
|
<SliderRoot
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'relative flex w-full touch-none select-none items-center',
|
'relative flex w-full touch-none select-none items-center data-[orientation=vertical]:flex-col data-[orientation=vertical]:w-2 data-[orientation=vertical]:h-full',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
>
|
>
|
||||||
<SliderTrack class="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
<SliderTrack class="relative h-2 w-full data-[orientation=vertical]:w-2 grow overflow-hidden rounded-full bg-secondary">
|
||||||
<SliderRange class="absolute h-full bg-primary" />
|
<SliderRange class="absolute h-full data-[orientation=vertical]:w-full bg-primary" />
|
||||||
</SliderTrack>
|
</SliderTrack>
|
||||||
<SliderThumb
|
<SliderThumb
|
||||||
v-for="(_, key) in modelValue"
|
v-for="(_, key) in modelValue"
|
||||||
|
|
|
||||||
31
apps/www/src/lib/registry/default/ui/stepper/Stepper.vue
Normal file
31
apps/www/src/lib/registry/default/ui/stepper/Stepper.vue
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StepperRootEmits, StepperRootProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { StepperRoot, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<StepperRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
const emits = defineEmits<StepperRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StepperRoot
|
||||||
|
v-slot="slotProps"
|
||||||
|
:class="cn(
|
||||||
|
'flex gap-2',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<slot v-bind="slotProps" />
|
||||||
|
</StepperRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StepperDescriptionProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { StepperDescription, useForwardProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<StepperDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StepperDescription v-slot="slotProps" v-bind="forwarded" :class="cn('text-xs text-muted-foreground', props.class)">
|
||||||
|
<slot v-bind="slotProps" />
|
||||||
|
</StepperDescription>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StepperIndicatorProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { StepperIndicator, useForwardProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<StepperIndicatorProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StepperIndicator
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn(
|
||||||
|
'inline-flex items-center justify-center rounded-full text-muted-foreground/50 w-10 h-10',
|
||||||
|
// Disabled
|
||||||
|
'group-data-[disabled]:text-muted-foreground group-data-[disabled]:opacity-50',
|
||||||
|
// Active
|
||||||
|
'group-data-[state=active]:bg-primary group-data-[state=active]:text-primary-foreground',
|
||||||
|
// Completed
|
||||||
|
'group-data-[state=completed]:bg-accent group-data-[state=completed]:text-accent-foreground',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</StepperIndicator>
|
||||||
|
</template>
|
||||||
27
apps/www/src/lib/registry/default/ui/stepper/StepperItem.vue
Normal file
27
apps/www/src/lib/registry/default/ui/stepper/StepperItem.vue
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StepperItemProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { StepperItem, useForwardProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<StepperItemProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StepperItem
|
||||||
|
v-slot="slotProps"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn('flex items-center gap-2 group data-[disabled]:pointer-events-none', props.class)"
|
||||||
|
>
|
||||||
|
<slot v-bind="slotProps" />
|
||||||
|
</StepperItem>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StepperSeparatorProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { StepperSeparator, useForwardProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<StepperSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StepperSeparator
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn(
|
||||||
|
'bg-muted',
|
||||||
|
// Disabled
|
||||||
|
'group-data-[disabled]:bg-muted group-data-[disabled]:opacity-50',
|
||||||
|
// Completed
|
||||||
|
'group-data-[state=completed]:bg-accent-foreground',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StepperTitleProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { StepperTitle, useForwardProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<StepperTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StepperTitle v-bind="forwarded" :class="cn('text-md font-semibold whitespace-nowrap', props.class)">
|
||||||
|
<slot />
|
||||||
|
</StepperTitle>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StepperTriggerProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { StepperTrigger, useForwardProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<StepperTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StepperTrigger
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn('p-2 flex flex-col items-center text-center gap-2 rounded-md', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</StepperTrigger>
|
||||||
|
</template>
|
||||||
7
apps/www/src/lib/registry/default/ui/stepper/index.ts
Normal file
7
apps/www/src/lib/registry/default/ui/stepper/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export { default as Stepper } from './Stepper.vue'
|
||||||
|
export { default as StepperDescription } from './StepperDescription.vue'
|
||||||
|
export { default as StepperIndicator } from './StepperIndicator.vue'
|
||||||
|
export { default as StepperItem } from './StepperItem.vue'
|
||||||
|
export { default as StepperSeparator } from './StepperSeparator.vue'
|
||||||
|
export { default as StepperTitle } from './StepperTitle.vue'
|
||||||
|
export { default as StepperTrigger } from './StepperTrigger.vue'
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { TabsList, type TabsListProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { TabsList, type TabsListProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<TabsListProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<TabsListProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ const delegatedProps = computed(() => {
|
||||||
<TabsList
|
<TabsList
|
||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
'inline-flex items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { TabsTrigger, type TabsTriggerProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { TabsTrigger, type TabsTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -22,6 +22,8 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<slot />
|
<span class="truncate">
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, type Ref, computed } from 'vue'
|
|
||||||
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'radix-vue'
|
|
||||||
import { createDecade, createYear, toDate } from 'radix-vue/date'
|
|
||||||
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
|
||||||
import { useVModel } from '@vueuse/core'
|
|
||||||
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/new-york/ui/calendar'
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/new-york/ui/calendar'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
|
@ -13,6 +8,11 @@ import {
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/lib/registry/new-york/ui/select'
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { createDecade, createYear, toDate } from 'radix-vue/date'
|
||||||
|
import { computed, type HTMLAttributes, type Ref } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
|
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||||
modelValue: undefined,
|
modelValue: undefined,
|
||||||
|
|
@ -72,7 +72,7 @@ const formatter = useDateFormatter('en')
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
:default-value="props.placeholder.year.toString()"
|
:default-value="placeholder.year.toString()"
|
||||||
@update:model-value="(v) => {
|
@update:model-value="(v) => {
|
||||||
if (!v || !placeholder) return;
|
if (!v || !placeholder) return;
|
||||||
if (Number(v) === placeholder?.year) return;
|
if (Number(v) === placeholder?.year) return;
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {
|
import type {
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
} from '@tanstack/vue-table'
|
} from '@tanstack/vue-table'
|
||||||
import {
|
|
||||||
FlexRender,
|
|
||||||
createColumnHelper,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
|
|
||||||
useVueTable,
|
|
||||||
} from '@tanstack/vue-table'
|
|
||||||
import { CaretSortIcon, ChevronDownIcon } from '@radix-icons/vue'
|
|
||||||
|
|
||||||
import { h, ref } from 'vue'
|
|
||||||
import DropdownAction from './DataTableDemoColumn.vue'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
|
|
@ -36,6 +24,19 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/lib/registry/new-york/ui/table'
|
} from '@/lib/registry/new-york/ui/table'
|
||||||
import { cn, valueUpdater } from '@/lib/utils'
|
import { cn, valueUpdater } from '@/lib/utils'
|
||||||
|
import { CaretSortIcon, ChevronDownIcon } from '@radix-icons/vue'
|
||||||
|
import {
|
||||||
|
createColumnHelper,
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
import DropdownAction from './DataTableDemoColumn.vue'
|
||||||
|
|
||||||
export interface Payment {
|
export interface Payment {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -133,6 +134,7 @@ const columns = [
|
||||||
|
|
||||||
return h('div', { class: 'relative' }, h(DropdownAction, {
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
payment,
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -142,6 +144,7 @@ const sorting = ref<SortingState>([])
|
||||||
const columnFilters = ref<ColumnFiltersState>([])
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
const columnVisibility = ref<VisibilityState>({})
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
const rowSelection = ref({})
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
data,
|
data,
|
||||||
|
|
@ -150,15 +153,18 @@ const table = useVueTable({
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
state: {
|
state: {
|
||||||
get sorting() { return sorting.value },
|
get sorting() { return sorting.value },
|
||||||
get columnFilters() { return columnFilters.value },
|
get columnFilters() { return columnFilters.value },
|
||||||
get columnVisibility() { return columnVisibility.value },
|
get columnVisibility() { return columnVisibility.value },
|
||||||
get rowSelection() { return rowSelection.value },
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
columnPinning: {
|
columnPinning: {
|
||||||
left: ['status'],
|
left: ['status'],
|
||||||
},
|
},
|
||||||
|
|
@ -213,21 +219,24 @@ const table = useVueTable({
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<template v-if="table.getRowModel().rows?.length">
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
<TableRow
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
v-for="row in table.getRowModel().rows"
|
<TableRow :data-state="row.getIsSelected() && 'selected'">
|
||||||
:key="row.id"
|
<TableCell
|
||||||
:data-state="row.getIsSelected() && 'selected'"
|
v-for="cell in row.getVisibleCells()" :key="cell.id" :data-pinned="cell.column.getIsPinned()"
|
||||||
>
|
:class="cn(
|
||||||
<TableCell
|
{ 'sticky bg-background/95': cell.column.getIsPinned() },
|
||||||
v-for="cell in row.getVisibleCells()" :key="cell.id" :data-pinned="cell.column.getIsPinned()"
|
cell.column.getIsPinned() === 'left' ? 'left-0' : 'right-0',
|
||||||
:class="cn(
|
)"
|
||||||
{ 'sticky bg-background/95': cell.column.getIsPinned() },
|
>
|
||||||
cell.column.getIsPinned() === 'left' ? 'left-0' : 'right-0',
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
)"
|
</TableCell>
|
||||||
>
|
</TableRow>
|
||||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
</TableCell>
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
</TableRow>
|
{{ JSON.stringify(row.original) }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<TableRow v-else>
|
<TableRow v-else>
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,10 @@
|
||||||
import type {
|
import type {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
} from '@tanstack/vue-table'
|
} from '@tanstack/vue-table'
|
||||||
import {
|
|
||||||
FlexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useVueTable,
|
|
||||||
} from '@tanstack/vue-table'
|
|
||||||
import { h, ref } from 'vue'
|
|
||||||
import { CaretSortIcon, ChevronDownIcon } from '@radix-icons/vue'
|
|
||||||
import DropdownAction from './DataTableDemoColumn.vue'
|
|
||||||
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
import {
|
import {
|
||||||
|
|
@ -26,6 +15,7 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
|
@ -35,6 +25,18 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/lib/registry/new-york/ui/table'
|
} from '@/lib/registry/new-york/ui/table'
|
||||||
import { valueUpdater } from '@/lib/utils'
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
import { CaretSortIcon, ChevronDownIcon } from '@radix-icons/vue'
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
import DropdownAction from './DataTableDemoColumn.vue'
|
||||||
|
|
||||||
export interface Payment {
|
export interface Payment {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -103,7 +105,7 @@ const columns: ColumnDef<Payment>[] = [
|
||||||
return h(Button, {
|
return h(Button, {
|
||||||
variant: 'ghost',
|
variant: 'ghost',
|
||||||
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
|
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
|
||||||
}, ['Email', h(CaretSortIcon, { class: 'ml-2 h-4 w-4' })])
|
}, () => ['Email', h(CaretSortIcon, { class: 'ml-2 h-4 w-4' })])
|
||||||
},
|
},
|
||||||
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
|
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
|
||||||
},
|
},
|
||||||
|
|
@ -130,6 +132,7 @@ const columns: ColumnDef<Payment>[] = [
|
||||||
|
|
||||||
return h(DropdownAction, {
|
return h(DropdownAction, {
|
||||||
payment,
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -139,6 +142,7 @@ const sorting = ref<SortingState>([])
|
||||||
const columnFilters = ref<ColumnFiltersState>([])
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
const columnVisibility = ref<VisibilityState>({})
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
const rowSelection = ref({})
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
data,
|
data,
|
||||||
|
|
@ -147,15 +151,18 @@ const table = useVueTable({
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
state: {
|
state: {
|
||||||
get sorting() { return sorting.value },
|
get sorting() { return sorting.value },
|
||||||
get columnFilters() { return columnFilters.value },
|
get columnFilters() { return columnFilters.value },
|
||||||
get columnVisibility() { return columnVisibility.value },
|
get columnVisibility() { return columnVisibility.value },
|
||||||
get rowSelection() { return rowSelection.value },
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -201,15 +208,18 @@ const table = useVueTable({
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<template v-if="table.getRowModel().rows?.length">
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
<TableRow
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
v-for="row in table.getRowModel().rows"
|
<TableRow :data-state="row.getIsSelected() && 'selected'">
|
||||||
:key="row.id"
|
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
:data-state="row.getIsSelected() && 'selected'"
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
>
|
</TableCell>
|
||||||
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
</TableRow>
|
||||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
</TableCell>
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
</TableRow>
|
{{ JSON.stringify(row.original) }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<TableRow v-else>
|
<TableRow v-else>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DotsHorizontalIcon } from '@radix-icons/vue'
|
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/lib/registry/new-york/ui/dropdown-menu'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||||
|
import { DotsHorizontalIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
payment: {
|
payment: {
|
||||||
|
|
@ -9,6 +9,10 @@ defineProps<{
|
||||||
}
|
}
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'expand'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
function copy(id: string) {
|
function copy(id: string) {
|
||||||
navigator.clipboard.writeText(id)
|
navigator.clipboard.writeText(id)
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +31,9 @@ function copy(id: string) {
|
||||||
<DropdownMenuItem @click="copy(payment.id)">
|
<DropdownMenuItem @click="copy(payment.id)">
|
||||||
Copy payment ID
|
Copy payment ID
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem @click="$emit('expand')">
|
||||||
|
Expand
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>View customer</DropdownMenuItem>
|
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||||
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||||
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/lib/registry/new-york/ui/table'
|
||||||
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { h, ref, shallowRef } from 'vue'
|
||||||
|
import DropdownAction from './DataTableDemoColumn.vue'
|
||||||
|
|
||||||
|
export interface Payment {
|
||||||
|
id: string
|
||||||
|
amount: number
|
||||||
|
status: 'pending' | 'processing' | 'success' | 'failed'
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = shallowRef<Payment[]>([
|
||||||
|
{
|
||||||
|
id: 'm5gr84i9',
|
||||||
|
amount: 316,
|
||||||
|
status: 'success',
|
||||||
|
email: 'ken99@yahoo.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3u1reuv4',
|
||||||
|
amount: 242,
|
||||||
|
status: 'success',
|
||||||
|
email: 'Abe45@gmail.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'derv1ws0',
|
||||||
|
amount: 837,
|
||||||
|
status: 'processing',
|
||||||
|
email: 'Monserrat44@gmail.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5kma53ae',
|
||||||
|
amount: 874,
|
||||||
|
status: 'success',
|
||||||
|
email: 'Silas22@gmail.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bhqecj4p',
|
||||||
|
amount: 721,
|
||||||
|
status: 'failed',
|
||||||
|
email: 'carmella@hotmail.com',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const columns: ColumnDef<Payment>[] = [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => h(Checkbox, {
|
||||||
|
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||||
|
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||||
|
'ariaLabel': 'Select all',
|
||||||
|
}),
|
||||||
|
cell: ({ row }) => h(Checkbox, {
|
||||||
|
'checked': row.getIsSelected(),
|
||||||
|
'onUpdate:checked': value => row.toggleSelected(!!value),
|
||||||
|
'ariaLabel': 'Select row',
|
||||||
|
}),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
|
header: 'Status',
|
||||||
|
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('status')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'email',
|
||||||
|
header: ({ column }) => {
|
||||||
|
return h(Button, {
|
||||||
|
variant: 'ghost',
|
||||||
|
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
|
||||||
|
}, () => ['Email', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
|
||||||
|
},
|
||||||
|
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'amount',
|
||||||
|
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const amount = Number.parseFloat(row.getValue('amount'))
|
||||||
|
|
||||||
|
// Format the amount as a dollar amount
|
||||||
|
const formatted = new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'USD',
|
||||||
|
}).format(amount)
|
||||||
|
|
||||||
|
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const payment = row.original
|
||||||
|
|
||||||
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const sorting = ref<SortingState>([])
|
||||||
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
|
const table = useVueTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
|
state: {
|
||||||
|
get sorting() { return sorting.value },
|
||||||
|
get columnFilters() { return columnFilters.value },
|
||||||
|
get columnVisibility() { return columnVisibility.value },
|
||||||
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const statuses: Payment['status'][] = ['pending', 'processing', 'success', 'failed']
|
||||||
|
function randomize() {
|
||||||
|
data.value = data.value.map(item => ({
|
||||||
|
...item,
|
||||||
|
status: statuses[Math.floor(Math.random() * statuses.length)],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="flex gap-2 items-center py-4">
|
||||||
|
<Input
|
||||||
|
class="max-w-52"
|
||||||
|
placeholder="Filter emails..."
|
||||||
|
:model-value="table.getColumn('email')?.getFilterValue() as string"
|
||||||
|
@update:model-value=" table.getColumn('email')?.setFilterValue($event)"
|
||||||
|
/>
|
||||||
|
<Button @click="randomize">
|
||||||
|
Randomize
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button variant="outline" class="ml-auto">
|
||||||
|
Columns <ChevronDown class="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())"
|
||||||
|
:key="column.id"
|
||||||
|
class="capitalize"
|
||||||
|
:checked="column.getIsVisible()"
|
||||||
|
@update:checked="(value) => {
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ column.id }}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||||
|
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
||||||
|
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
|
<TableRow :data-state="row.getIsSelected() && 'selected'">
|
||||||
|
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
|
{{ JSON.stringify(row.original) }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<TableRow v-else>
|
||||||
|
<TableCell
|
||||||
|
:colspan="columns.length"
|
||||||
|
class="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end space-x-2 py-4">
|
||||||
|
<div class="flex-1 text-sm text-muted-foreground">
|
||||||
|
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
||||||
|
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div class="space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!table.getCanPreviousPage()"
|
||||||
|
@click="table.previousPage()"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!table.getCanNextPage()"
|
||||||
|
@click="table.nextPage()"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
79
apps/www/src/lib/registry/new-york/example/DialogForm.vue
Normal file
79
apps/www/src/lib/registry/new-york/example/DialogForm.vue
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/dialog'
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
|
|
||||||
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
username: z.string().min(2).max(50),
|
||||||
|
}))
|
||||||
|
|
||||||
|
function onSubmit(values: any) {
|
||||||
|
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 v-slot="{ submitForm }" as="" keep-values :validation-schema="formSchema" @submit="onSubmit">
|
||||||
|
<Dialog>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<form @submit="submitForm">
|
||||||
|
<FormField v-slot="{ componentField }" name="username">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="text" placeholder="shadcn" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
This is your public display name.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" form="dialogForm">
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ListItem from './NavigationMenuDemoItem.vue'
|
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuContent,
|
NavigationMenuContent,
|
||||||
|
|
@ -73,15 +72,45 @@ const components: { title: string, href: string, description: string }[] = [
|
||||||
</a>
|
</a>
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
</li>
|
</li>
|
||||||
<ListItem href="/docs" title="Introduction">
|
<li>
|
||||||
Re-usable components built using Radix UI and Tailwind CSS.
|
<NavigationMenuLink as-child>
|
||||||
</ListItem>
|
<a
|
||||||
<ListItem href="/docs/installation" title="Installation">
|
href="/docs"
|
||||||
How to install dependencies and structure your app.
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
</ListItem>
|
>
|
||||||
<ListItem href="/docs/primitives/typography" title="Typography">
|
<div class="text-sm font-medium leading-none">Introduction</div>
|
||||||
Styles for headings, paragraphs, lists...etc
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
</ListItem>
|
Re-usable components built using Radix UI and Tailwind CSS.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<a
|
||||||
|
href="/docs/installation"
|
||||||
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
|
>
|
||||||
|
<div class="text-sm font-medium leading-none">Installation</div>
|
||||||
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
How to install dependencies and structure your app.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<a
|
||||||
|
href="/docs/primitives/typography"
|
||||||
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
|
>
|
||||||
|
<div class="text-sm font-medium leading-none">Typography</div>
|
||||||
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
Styles for headings, paragraphs, lists...etc
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</NavigationMenuContent>
|
</NavigationMenuContent>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
|
@ -89,14 +118,19 @@ const components: { title: string, href: string, description: string }[] = [
|
||||||
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
|
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
|
||||||
<NavigationMenuContent>
|
<NavigationMenuContent>
|
||||||
<ul class="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
|
<ul class="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
|
||||||
<ListItem
|
<li v-for="component in components" :key="component.title">
|
||||||
v-for="component in components"
|
<NavigationMenuLink as-child>
|
||||||
:key="component.title"
|
<a
|
||||||
:title="component.title"
|
:href="component.href"
|
||||||
:href="component.href"
|
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||||
>
|
>
|
||||||
{{ component.description }}
|
<div class="text-sm font-medium leading-none">{{ component.title }}</div>
|
||||||
</ListItem>
|
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
{{ component.description }}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</NavigationMenuContent>
|
</NavigationMenuContent>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import {
|
|
||||||
NavigationMenuLink,
|
|
||||||
} from '@/lib/registry/new-york/ui/navigation-menu'
|
|
||||||
|
|
||||||
defineProps<{ title?: string, href?: string }>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<li>
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<a
|
|
||||||
:href="href"
|
|
||||||
:class="cn(
|
|
||||||
'block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
|
|
||||||
$attrs.class ?? '',
|
|
||||||
)"
|
|
||||||
>
|
|
||||||
<div class="text-sm font-medium leading-none">{{ title }}</div>
|
|
||||||
<p class="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
|
||||||
<slot />
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
<script setup lang="ts">
|
<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 { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -22,6 +17,11 @@ import {
|
||||||
} from '@/lib/registry/new-york/ui/number-field'
|
} from '@/lib/registry/new-york/ui/number-field'
|
||||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
payment: z.number().min(10, 'Min 10 euros to send payment').max(5000, 'Max 5000 euros to send payment'),
|
payment: z.number().min(10, 'Min 10 euros to send payment').max(5000, 'Max 5000 euros to send payment'),
|
||||||
}))
|
}))
|
||||||
|
|
@ -43,7 +43,7 @@ const onSubmit = handleSubmit((values) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||||
<FormField name="payment">
|
<FormField v-slot="{ value }" name="payment">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Payment</FormLabel>
|
<FormLabel>Payment</FormLabel>
|
||||||
<NumberField
|
<NumberField
|
||||||
|
|
@ -55,6 +55,7 @@ const onSubmit = handleSubmit((values) => {
|
||||||
currencyDisplay: 'code',
|
currencyDisplay: 'code',
|
||||||
currencySign: 'accounting',
|
currencySign: 'accounting',
|
||||||
}"
|
}"
|
||||||
|
:model-value="value"
|
||||||
@update:model-value="(v) => {
|
@update:model-value="(v) => {
|
||||||
if (v) {
|
if (v) {
|
||||||
setFieldValue('payment', v)
|
setFieldValue('payment', v)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,4 @@
|
||||||
<script setup lang="ts">
|
<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 {
|
|
||||||
PinInput,
|
|
||||||
PinInputGroup,
|
|
||||||
PinInputInput,
|
|
||||||
} from '@/lib/registry/new-york/ui/pin-input'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -17,7 +8,16 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
|
import {
|
||||||
|
PinInput,
|
||||||
|
PinInputGroup,
|
||||||
|
PinInputInput,
|
||||||
|
} from '@/lib/registry/new-york/ui/pin-input'
|
||||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
pin: z.array(z.coerce.string()).length(5, { message: 'Invalid input' }),
|
pin: z.array(z.coerce.string()).length(5, { message: 'Invalid input' }),
|
||||||
|
|
@ -48,7 +48,7 @@ const handleComplete = (e: string[]) => console.log(e.join(''))
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<PinInput
|
<PinInput
|
||||||
id="pin-input"
|
id="pin-input"
|
||||||
v-model="value!"
|
:model-value="value"
|
||||||
placeholder="○"
|
placeholder="○"
|
||||||
class="flex gap-2 items-center mt-1"
|
class="flex gap-2 items-center mt-1"
|
||||||
otp
|
otp
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||||
An open-source UI component library.
|
An open-source UI component library.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator class="my-4" />
|
<Separator class="my-4" label="Or" />
|
||||||
<div class="flex h-5 items-center space-x-4 text-sm">
|
<div class="flex h-5 items-center space-x-4 text-sm">
|
||||||
<div>Blog</div>
|
<div>Blog</div>
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
|
|
|
||||||
56
apps/www/src/lib/registry/new-york/example/StepperDemo.vue
Normal file
56
apps/www/src/lib/registry/new-york/example/StepperDemo.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Stepper, StepperDescription, StepperIndicator, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/new-york/ui/stepper'
|
||||||
|
|
||||||
|
import { BookUser, Check, CreditCard, Truck } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const steps = [{
|
||||||
|
step: 1,
|
||||||
|
title: 'Address',
|
||||||
|
description: 'Add your address here',
|
||||||
|
icon: BookUser,
|
||||||
|
}, {
|
||||||
|
step: 2,
|
||||||
|
title: 'Shipping',
|
||||||
|
description: 'Set your preferred shipping method',
|
||||||
|
icon: Truck,
|
||||||
|
}, {
|
||||||
|
step: 3,
|
||||||
|
title: 'Payment',
|
||||||
|
description: 'Add any payment information you have',
|
||||||
|
icon: CreditCard,
|
||||||
|
}, {
|
||||||
|
step: 4,
|
||||||
|
title: 'Checkout',
|
||||||
|
description: 'Confirm your order',
|
||||||
|
icon: Check,
|
||||||
|
}]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper>
|
||||||
|
<StepperItem
|
||||||
|
v-for="item in steps"
|
||||||
|
:key="item.step"
|
||||||
|
class="basis-1/4"
|
||||||
|
:step="item.step"
|
||||||
|
>
|
||||||
|
<StepperTrigger>
|
||||||
|
<StepperIndicator>
|
||||||
|
<component :is="item.icon" class="w-4 h-4" />
|
||||||
|
</StepperIndicator>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<StepperTitle>
|
||||||
|
{{ item.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription>
|
||||||
|
{{ item.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperTrigger>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="item.step !== steps[steps.length - 1].step"
|
||||||
|
class="w-full h-px"
|
||||||
|
/>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
223
apps/www/src/lib/registry/new-york/example/StepperForm.vue
Normal file
223
apps/www/src/lib/registry/new-york/example/StepperForm.vue
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||||
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
|
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/new-york/ui/stepper'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
import { CheckIcon, CircleIcon, DotIcon } from '@radix-icons/vue'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
const formSchema = [
|
||||||
|
z.object({
|
||||||
|
fullName: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
password: z.string().min(2).max(50),
|
||||||
|
confirmPassword: z.string(),
|
||||||
|
}).refine(
|
||||||
|
(values) => {
|
||||||
|
return values.password === values.confirmPassword
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Passwords must match!',
|
||||||
|
path: ['confirmPassword'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
z.object({
|
||||||
|
favoriteDrink: z.union([z.literal('coffee'), z.literal('tea'), z.literal('soda')]),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
const stepIndex = ref(1)
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
title: 'Your details',
|
||||||
|
description: 'Provide your name and email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
title: 'Your password',
|
||||||
|
description: 'Choose a password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 3,
|
||||||
|
title: 'Your Favorite Drink',
|
||||||
|
description: 'Choose a drink',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function onSubmit(values: any) {
|
||||||
|
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
|
||||||
|
v-slot="{ meta, values, validate }"
|
||||||
|
as="" keep-values :validation-schema="toTypedSchema(formSchema[stepIndex - 1])"
|
||||||
|
>
|
||||||
|
<Stepper v-slot="{ isNextDisabled, isPrevDisabled, nextStep, prevStep }" v-model="stepIndex" class="block w-full">
|
||||||
|
<form
|
||||||
|
@submit="(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
validate()
|
||||||
|
|
||||||
|
if (stepIndex === steps.length && meta.valid) {
|
||||||
|
onSubmit(values)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="flex w-full flex-start gap-2">
|
||||||
|
<StepperItem
|
||||||
|
v-for="step in steps"
|
||||||
|
:key="step.step"
|
||||||
|
v-slot="{ state }"
|
||||||
|
class="relative flex w-full flex-col items-center justify-center"
|
||||||
|
:step="step.step"
|
||||||
|
>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="step.step !== steps[steps.length - 1].step"
|
||||||
|
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StepperTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||||
|
size="icon"
|
||||||
|
class="z-10 rounded-full shrink-0"
|
||||||
|
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
|
||||||
|
:disabled="state !== 'completed' && !meta.valid"
|
||||||
|
>
|
||||||
|
<CheckIcon v-if="state === 'completed'" class="size-5" />
|
||||||
|
<CircleIcon v-if="state === 'active'" />
|
||||||
|
<DotIcon v-if="state === 'inactive'" />
|
||||||
|
</Button>
|
||||||
|
</StepperTrigger>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-col items-center text-center">
|
||||||
|
<StepperTitle
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="text-sm font-semibold transition lg:text-base"
|
||||||
|
>
|
||||||
|
{{ step.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
|
||||||
|
>
|
||||||
|
{{ step.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 mt-4">
|
||||||
|
<template v-if="stepIndex === 1">
|
||||||
|
<FormField v-slot="{ componentField }" name="fullName">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Full Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="text" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="email">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="email " v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="stepIndex === 2">
|
||||||
|
<FormField v-slot="{ componentField }" name="password">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Password</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="password" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="confirmPassword">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Confirm Password</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="password" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="stepIndex === 3">
|
||||||
|
<FormField v-slot="{ componentField }" name="favoriteDrink">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Drink</FormLabel>
|
||||||
|
|
||||||
|
<Select v-bind="componentField">
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a drink" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="coffee">
|
||||||
|
Coffe
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="tea">
|
||||||
|
Tea
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="soda">
|
||||||
|
Soda
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<Button :disabled="isPrevDisabled" variant="outline" size="sm" @click="prevStep()">
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Button v-if="stepIndex !== 3" :type="meta.valid ? 'button' : 'submit'" :disabled="isNextDisabled" size="sm" @click="meta.valid && nextStep()">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="stepIndex === 3" size="sm" type="submit"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Stepper>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
||||||
|
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/new-york/ui/stepper'
|
||||||
|
import { CheckIcon, CircleIcon, DotIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
title: 'Your details',
|
||||||
|
description: 'Provide your name and email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
title: 'Company details',
|
||||||
|
description: 'A few details about your company',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 3,
|
||||||
|
title: 'Invite your team',
|
||||||
|
description: 'Start collaborating with your team',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper class="flex w-full items-start gap-2">
|
||||||
|
<StepperItem
|
||||||
|
v-for="step in steps"
|
||||||
|
:key="step.step"
|
||||||
|
v-slot="{ state }"
|
||||||
|
class="relative flex w-full flex-col items-center justify-center"
|
||||||
|
:step="step.step"
|
||||||
|
>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="step.step !== steps[steps.length - 1].step"
|
||||||
|
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StepperTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||||
|
size="icon"
|
||||||
|
class="z-10 rounded-full shrink-0"
|
||||||
|
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
|
||||||
|
>
|
||||||
|
<CheckIcon v-if="state === 'completed'" class="size-5" />
|
||||||
|
<CircleIcon v-if="state === 'active'" />
|
||||||
|
<DotIcon v-if="state === 'inactive'" />
|
||||||
|
</Button>
|
||||||
|
</StepperTrigger>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-col items-center text-center">
|
||||||
|
<StepperTitle
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="text-sm font-semibold transition lg:text-base"
|
||||||
|
>
|
||||||
|
{{ step.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
|
||||||
|
>
|
||||||
|
{{ step.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
||||||
|
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/lib/registry/new-york/ui/stepper'
|
||||||
|
import { CheckIcon, CircleIcon, DotIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
title: 'Your details',
|
||||||
|
description:
|
||||||
|
'Provide your name and email address. We will use this information to create your account',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
title: 'Company details',
|
||||||
|
description: 'A few details about your company will help us personalize your experience',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 3,
|
||||||
|
title: 'Invite your team',
|
||||||
|
description:
|
||||||
|
'Start collaborating with your team by inviting them to join your account. You can skip this step and invite them later',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper orientation="vertical" class="mx-auto flex w-full max-w-md flex-col justify-start gap-10">
|
||||||
|
<StepperItem
|
||||||
|
v-for="step in steps"
|
||||||
|
:key="step.step"
|
||||||
|
v-slot="{ state }"
|
||||||
|
class="relative flex w-full items-start gap-6"
|
||||||
|
:step="step.step"
|
||||||
|
>
|
||||||
|
<StepperSeparator
|
||||||
|
v-if="step.step !== steps[steps.length - 1].step"
|
||||||
|
class="absolute left-[18px] top-[38px] block h-[105%] w-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StepperTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||||
|
size="icon"
|
||||||
|
class="z-10 rounded-full shrink-0"
|
||||||
|
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
|
||||||
|
>
|
||||||
|
<CheckIcon v-if="state === 'completed'" class="size-5" />
|
||||||
|
<CircleIcon v-if="state === 'active'" />
|
||||||
|
<DotIcon v-if="state === 'inactive'" />
|
||||||
|
</Button>
|
||||||
|
</StepperTrigger>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<StepperTitle
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="text-sm font-semibold transition lg:text-base"
|
||||||
|
>
|
||||||
|
{{ step.title }}
|
||||||
|
</StepperTitle>
|
||||||
|
<StepperDescription
|
||||||
|
:class="[state === 'active' && 'text-primary']"
|
||||||
|
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
|
||||||
|
>
|
||||||
|
{{ step.description }}
|
||||||
|
</StepperDescription>
|
||||||
|
</div>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/lib/registry/new-york/ui/card'
|
||||||
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/tabs'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Tabs default-value="account" class="w-[400px]" orientation="vertical">
|
||||||
|
<TabsList class="grid w-full grid-cols-1">
|
||||||
|
<TabsTrigger value="account">
|
||||||
|
Account
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="password">
|
||||||
|
Password
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="account">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Account</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Make changes to your account here. Click save when you're done.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent class="space-y-2">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="name">Name</Label>
|
||||||
|
<Input id="name" default-value="Pedro Duarte" />
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="username">Username</Label>
|
||||||
|
<Input id="username" default-value="@peduarte" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Save changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="password">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Password</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Change your password here. After saving, you'll be logged out.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent class="space-y-2">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="current">Current password</Label>
|
||||||
|
<Input id="current" type="password" />
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="new">New password</Label>
|
||||||
|
<Input id="new" type="password" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Save password</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</template>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import { ComboboxAnchor, ComboboxInput, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
|
||||||
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/lib/registry/new-york/ui/command'
|
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/lib/registry/new-york/ui/command'
|
||||||
import { TagsInput, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from '@/lib/registry/new-york/ui/tags-input'
|
import { TagsInput, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from '@/lib/registry/new-york/ui/tags-input'
|
||||||
|
import { ComboboxAnchor, ComboboxContent, ComboboxInput, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const frameworks = [
|
const frameworks = [
|
||||||
{ value: 'next.js', label: 'Next.js' },
|
{ value: 'next.js', label: 'Next.js' },
|
||||||
|
|
@ -28,7 +28,7 @@ const filteredFrameworks = computed(() => frameworks.filter(i => !modelValue.val
|
||||||
</TagsInputItem>
|
</TagsInputItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ComboboxRoot v-model="modelValue" v-model:open="open" v-model:searchTerm="searchTerm" class="w-full">
|
<ComboboxRoot v-model="modelValue" v-model:open="open" v-model:search-term="searchTerm" class="w-full">
|
||||||
<ComboboxAnchor as-child>
|
<ComboboxAnchor as-child>
|
||||||
<ComboboxInput placeholder="Framework..." as-child>
|
<ComboboxInput placeholder="Framework..." as-child>
|
||||||
<TagsInputInput class="w-full px-3" :class="modelValue.length > 0 ? 'mt-2' : ''" @keydown.enter.prevent />
|
<TagsInputInput class="w-full px-3" :class="modelValue.length > 0 ? 'mt-2' : ''" @keydown.enter.prevent />
|
||||||
|
|
@ -36,29 +36,31 @@ const filteredFrameworks = computed(() => frameworks.filter(i => !modelValue.val
|
||||||
</ComboboxAnchor>
|
</ComboboxAnchor>
|
||||||
|
|
||||||
<ComboboxPortal>
|
<ComboboxPortal>
|
||||||
<CommandList
|
<ComboboxContent>
|
||||||
position="popper"
|
<CommandList
|
||||||
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
position="popper"
|
||||||
>
|
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
||||||
<CommandEmpty />
|
>
|
||||||
<CommandGroup>
|
<CommandEmpty />
|
||||||
<CommandItem
|
<CommandGroup>
|
||||||
v-for="framework in filteredFrameworks" :key="framework.value" :value="framework.label"
|
<CommandItem
|
||||||
@select.prevent="(ev) => {
|
v-for="framework in filteredFrameworks" :key="framework.value" :value="framework.label"
|
||||||
if (typeof ev.detail.value === 'string') {
|
@select.prevent="(ev) => {
|
||||||
searchTerm = ''
|
if (typeof ev.detail.value === 'string') {
|
||||||
modelValue.push(ev.detail.value)
|
searchTerm = ''
|
||||||
}
|
modelValue.push(ev.detail.value)
|
||||||
|
}
|
||||||
|
|
||||||
if (filteredFrameworks.length === 0) {
|
if (filteredFrameworks.length === 0) {
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ framework.label }}
|
{{ framework.label }}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
|
</ComboboxContent>
|
||||||
</ComboboxPortal>
|
</ComboboxPortal>
|
||||||
</ComboboxRoot>
|
</ComboboxRoot>
|
||||||
</TagsInput>
|
</TagsInput>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
|
import { TagsInput, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from '@/lib/registry/new-york/ui/tags-input'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
fruits: z.array(z.string()).min(1).max(3),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { handleSubmit } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
initialValues: {
|
||||||
|
fruits: ['Apple', 'Banana'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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="{ value }" name="fruits">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Fruits</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<TagsInput :model-value="value">
|
||||||
|
<TagsInputItem v-for="item in value" :key="item" :value="item">
|
||||||
|
<TagsInputItemText />
|
||||||
|
<TagsInputItemDelete />
|
||||||
|
</TagsInputItem>
|
||||||
|
|
||||||
|
<TagsInputInput placeholder="Fruits..." />
|
||||||
|
</TagsInput>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Select your favorite fruits.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts" generic="T extends ZodObjectOrWrapped">
|
<script setup lang="ts" generic="T extends ZodObjectOrWrapped">
|
||||||
import { computed, toRefs } from 'vue'
|
|
||||||
import type { ZodAny, z } from 'zod'
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
|
||||||
import type { FormContext, GenericObject } from 'vee-validate'
|
import type { FormContext, GenericObject } from 'vee-validate'
|
||||||
import { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils'
|
import type { z, ZodAny } from 'zod'
|
||||||
import type { Config, ConfigItem, Dependency, Shape } from './interface'
|
import type { Config, ConfigItem, Dependency, Shape } from './interface'
|
||||||
|
import { Form } from '@/lib/registry/new-york/ui/form'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { computed, toRefs } from 'vue'
|
||||||
import AutoFormField from './AutoFormField.vue'
|
import AutoFormField from './AutoFormField.vue'
|
||||||
import { provideDependencies } from './dependencies'
|
import { provideDependencies } from './dependencies'
|
||||||
import { Form } from '@/lib/registry/new-york/ui/form'
|
import { getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema, type ZodObjectOrWrapped } from './utils'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
schema: T
|
schema: T
|
||||||
|
|
@ -17,7 +17,7 @@ const props = defineProps<{
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
submit: [event: GenericObject]
|
submit: [event: z.infer<T>]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { dependencies } = toRefs(props)
|
const { dependencies } = toRefs(props)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CalendarCell
|
<CalendarCell
|
||||||
:class="cn('relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50', props.class)"
|
:class="cn('relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-view])]:bg-accent/50', props.class)"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
||||||
// Unavailable
|
// Unavailable
|
||||||
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
||||||
// Outside months
|
// Outside months
|
||||||
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
|
'data-[outside-view]:text-muted-foreground data-[outside-view]:opacity-50 [&[data-outside-view][data-selected]]:bg-accent/50 [&[data-outside-view][data-selected]]:text-muted-foreground [&[data-outside-view][data-selected]]:opacity-30',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useProvideCarousel } from './useCarousel'
|
|
||||||
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'
|
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useProvideCarousel } from './useCarousel'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
|
@ -9,9 +9,17 @@ const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
||||||
|
|
||||||
const emits = defineEmits<CarouselEmits>()
|
const emits = defineEmits<CarouselEmits>()
|
||||||
|
|
||||||
const carouselArgs = useProvideCarousel(props, emits)
|
const { canScrollNext, canScrollPrev, carouselApi, carouselRef, orientation, scrollNext, scrollPrev } = useProvideCarousel(props, emits)
|
||||||
|
|
||||||
defineExpose(carouselArgs)
|
defineExpose({
|
||||||
|
canScrollNext,
|
||||||
|
canScrollPrev,
|
||||||
|
carouselApi,
|
||||||
|
carouselRef,
|
||||||
|
orientation,
|
||||||
|
scrollNext,
|
||||||
|
scrollPrev,
|
||||||
|
})
|
||||||
|
|
||||||
function onKeyDown(event: KeyboardEvent) {
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
|
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
|
||||||
|
|
@ -19,14 +27,14 @@ function onKeyDown(event: KeyboardEvent) {
|
||||||
|
|
||||||
if (event.key === prevKey) {
|
if (event.key === prevKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
carouselArgs.scrollPrev()
|
scrollPrev()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === nextKey) {
|
if (event.key === nextKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
carouselArgs.scrollNext()
|
scrollNext()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -39,6 +47,6 @@ function onKeyDown(event: KeyboardEvent) {
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keydown="onKeyDown"
|
@keydown="onKeyDown"
|
||||||
>
|
>
|
||||||
<slot v-bind="carouselArgs" />
|
<slot :can-scroll-next :can-scroll-prev :carousel-api :carousel-ref :orientation :scroll-next :scroll-prev />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user