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

This commit is contained in:
zernonia 2023-11-13 17:42:09 +08:00
commit 5a9bd2933b
18 changed files with 419 additions and 20 deletions

View File

@ -15,6 +15,6 @@ export const siteConfig = {
export const announcementConfig = { export const announcementConfig = {
icon: '✨', icon: '✨',
title: 'New Toast component', title: 'VSCode extension',
link: '/docs/components/toast', link: '/docs/installation.html#vscode-extension',
} }

View File

@ -9,6 +9,9 @@ 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 = {} let files = {}
files = constructFiles(componentName, style, sources) files = constructFiles(componentName, style, sources)
files['.codesandbox/Dockerfile'] = {
content: 'FROM node:18',
}
return getParameters({ files, template: 'node' }) return getParameters({ files, template: 'node' })
} }
@ -121,7 +124,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
'package.json': { 'package.json': {
content: { content: {
name: `shadcn-vue-${componentName.toLowerCase().replace(/ /g, '-')}`, name: `shadcn-vue-${componentName.toLowerCase().replace(/ /g, '-')}`,
scripts: { start: registryDependencies ? `shadcn-vue add ${registryDependencies.join(' ')} -y && vite` : 'vite' }, scripts: { start: `shadcn-vue add ${registryDependencies.join(' ')} -y && vite` },
dependencies, dependencies,
devDependencies, devDependencies,
}, },

View File

@ -30,6 +30,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/AlertDialogDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/AlertDialogDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/AlertDialogDemo.vue'], files: ['../src/lib/registry/default/example/AlertDialogDemo.vue'],
}, },
AreaChartDemo: {
name: 'AreaChartDemo',
type: 'components:example',
registryDependencies: ['chart-area'],
component: () => import('../src/lib/registry/default/example/AreaChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/AreaChartDemo.vue'],
},
AspectRatioDemo: { AspectRatioDemo: {
name: 'AspectRatioDemo', name: 'AspectRatioDemo',
type: 'components:example', type: 'components:example',
@ -72,6 +79,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/BadgeSecondaryDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/BadgeSecondaryDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/BadgeSecondaryDemo.vue'], files: ['../src/lib/registry/default/example/BadgeSecondaryDemo.vue'],
}, },
BarChartDemo: {
name: 'BarChartDemo',
type: 'components:example',
registryDependencies: ['chart-bar'],
component: () => import('../src/lib/registry/default/example/BarChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/BarChartDemo.vue'],
},
ButtonAsChildDemo: { ButtonAsChildDemo: {
name: 'ButtonAsChildDemo', name: 'ButtonAsChildDemo',
type: 'components:example', type: 'components:example',
@ -331,6 +345,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'],
}, },
DonutChartDemo: {
name: 'DonutChartDemo',
type: 'components:example',
registryDependencies: ['chart-donut'],
component: () => import('../src/lib/registry/default/example/DonutChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/DonutChartDemo.vue'],
},
DropdownMenuDemo: { DropdownMenuDemo: {
name: 'DropdownMenuDemo', name: 'DropdownMenuDemo',
type: 'components:example', type: 'components:example',
@ -401,6 +422,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/LabelDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/LabelDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/LabelDemo.vue'], files: ['../src/lib/registry/default/example/LabelDemo.vue'],
}, },
LineChartDemo: {
name: 'LineChartDemo',
type: 'components:example',
registryDependencies: ['chart-line'],
component: () => import('../src/lib/registry/default/example/LineChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/LineChartDemo.vue'],
},
MenubarDemo: { MenubarDemo: {
name: 'MenubarDemo', name: 'MenubarDemo',
type: 'components:example', type: 'components:example',
@ -823,6 +851,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/AlertDialogDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/AlertDialogDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/AlertDialogDemo.vue'], files: ['../src/lib/registry/new-york/example/AlertDialogDemo.vue'],
}, },
AreaChartDemo: {
name: 'AreaChartDemo',
type: 'components:example',
registryDependencies: ['chart-area'],
component: () => import('../src/lib/registry/new-york/example/AreaChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/AreaChartDemo.vue'],
},
AspectRatioDemo: { AspectRatioDemo: {
name: 'AspectRatioDemo', name: 'AspectRatioDemo',
type: 'components:example', type: 'components:example',
@ -865,6 +900,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue'], files: ['../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue'],
}, },
BarChartDemo: {
name: 'BarChartDemo',
type: 'components:example',
registryDependencies: ['chart-bar'],
component: () => import('../src/lib/registry/new-york/example/BarChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/BarChartDemo.vue'],
},
ButtonAsChildDemo: { ButtonAsChildDemo: {
name: 'ButtonAsChildDemo', name: 'ButtonAsChildDemo',
type: 'components:example', type: 'components:example',
@ -1124,6 +1166,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'],
}, },
DonutChartDemo: {
name: 'DonutChartDemo',
type: 'components:example',
registryDependencies: ['chart-donut'],
component: () => import('../src/lib/registry/new-york/example/DonutChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/DonutChartDemo.vue'],
},
DropdownMenuDemo: { DropdownMenuDemo: {
name: 'DropdownMenuDemo', name: 'DropdownMenuDemo',
type: 'components:example', type: 'components:example',
@ -1194,6 +1243,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/LabelDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/LabelDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/LabelDemo.vue'], files: ['../src/lib/registry/new-york/example/LabelDemo.vue'],
}, },
LineChartDemo: {
name: 'LineChartDemo',
type: 'components:example',
registryDependencies: ['chart-line'],
component: () => import('../src/lib/registry/new-york/example/LineChartDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/LineChartDemo.vue'],
},
MenubarDemo: { MenubarDemo: {
name: 'MenubarDemo', name: 'MenubarDemo',
type: 'components:example', type: 'components:example',

View File

@ -93,3 +93,15 @@ To configure import aliases, you can use the following `jsconfig.json`:
} }
} }
} }
```
## VSCode extension
Install the [shadcn-vue](https://marketplace.visualstudio.com/items?itemName=Selemondev.shadcn-vue) extension by [@selemondev](https://github.com/selemondev) in Visual Studio Code to easily add Shadcn Vue components to your project.
This extension offers a range of features:
- Ability to initialize the Shadcn Vue CLI
- Install components
- Open documentation
- Navigate to a specific component's documentation page directly from your IDE.
- Handy snippets for quick and straightforward component imports and markup.

View File

@ -16,4 +16,3 @@ import { Label } from '@/lib/registry/new-york/ui/label'
</CardContent> </CardContent>
</Card> </Card>
</template> </template>
@/lib/registry/default/example/DatePickerWithRange.vue

View File

@ -53,19 +53,6 @@
], ],
"type": "components:ui" "type": "components:ui"
}, },
{
"name": "area-chart",
"dependencies": [
"@unovis/vue",
"@unovis/ts"
],
"registryDependencies": [],
"files": [
"ui/area-chart/AreaChart.vue",
"ui/area-chart/index.ts"
],
"type": "components:ui"
},
{ {
"name": "aspect-ratio", "name": "aspect-ratio",
"dependencies": [ "dependencies": [
@ -151,6 +138,94 @@
], ],
"type": "components:ui" "type": "components:ui"
}, },
{
"name": "chart",
"dependencies": [
"@unovis/vue",
"@unovis/ts"
],
"registryDependencies": [
"chart",
"button",
"card"
],
"files": [
"ui/chart/ChartCrosshair.vue",
"ui/chart/ChartLegend.vue",
"ui/chart/ChartSingleTooltip.vue",
"ui/chart/ChartTooltip.vue",
"ui/chart/index.ts"
],
"type": "components:ui"
},
{
"name": "chart-area",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-area/AreaChart.vue",
"ui/chart-area/index.ts"
],
"type": "components:ui"
},
{
"name": "chart-bar",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-bar/BarChart.vue",
"ui/chart-bar/index.ts"
],
"type": "components:ui"
},
{
"name": "chart-donut",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-donut/DonutChart.vue",
"ui/chart-donut/index.ts"
],
"type": "components:ui"
},
{
"name": "chart-line",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-line/LineChart.vue",
"ui/chart-line/index.ts"
],
"type": "components:ui"
},
{ {
"name": "checkbox", "name": "checkbox",
"dependencies": [ "dependencies": [

View File

@ -11,7 +11,7 @@
}, },
{ {
"name": "index.ts", "name": "index.ts",
"content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-10 px-4 py-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n" "content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-10 px-4 py-2',\n xs: 'h-7 rounded px-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n"
} }
], ],
"type": "components:ui" "type": "components:ui"

View File

@ -0,0 +1,23 @@
{
"name": "chart-area",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "AreaChart.vue",
"content": "<script setup lang=\"ts\">\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'\nimport { Area, Axis, Line } from '@unovis/ts'\nimport { ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n categories: string[]\n index: string\n colors?: string[]\n filterOpacity?: number\n xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n showXAxis?: boolean\n showYAxis?: boolean\n showTooltip?: boolean\n showLegend?: boolean\n showGridLine?: boolean\n showGradiant?: boolean\n}>(), {\n colors: () => defaultColors,\n filterOpacity: 0.2,\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showLegend: true,\n showGridLine: true,\n showGradiant: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({\n name: category,\n color: props.colors[i],\n inactive: false,\n})))\n\nconst isMounted = useMounted()\n\nfunction handleLegendItemClick(d: BulletLegendItemInterface, i: number) {\n // do something when clicked on legend\n}\n</script>\n\n<template>\n <div :class=\"cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')\">\n <ChartLegend v-if=\"showLegend\" v-model:items=\"legendItems\" @legend-item-click=\"handleLegendItemClick\" />\n\n <VisXYContainer :style=\"{ height: isMounted ? '100%' : 'auto' }\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <svg width=\"0\" height=\"0\">\n <defs>\n <linearGradient v-for=\"(color, i) in colors\" :id=\"`color-${i}`\" :key=\"i\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <template v-if=\"showGradiant\">\n <stop offset=\"5%\" :stop-color=\"color\" stop-opacity=\"0.4\" />\n <stop offset=\"95%\" :stop-color=\"color\" stop-opacity=\"0\" />\n </template>\n <template v-else>\n <stop offset=\"0%\" :stop-color=\"color\" />\n </template>\n </linearGradient>\n </defs>\n </svg>\n\n <ChartCrosshair v-if=\"showTooltip\" :colors=\"colors\" :items=\"legendItems\" :index=\"index\" />\n\n <template v-for=\"(category, i) in categories\" :key=\"category\">\n <VisArea\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n color=\"auto\"\n :attributes=\"{\n [Area.selectors.area]: {\n fill: `url(#color-${i})`,\n },\n }\"\n :opacity=\"legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1\"\n />\n\n <VisLine\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n :color=\"colors[i]\"\n :attributes=\"{\n [Line.selectors.line]: {\n opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,\n },\n }\"\n />\n </template>\n\n <VisAxis\n v-if=\"showXAxis\"\n type=\"x\"\n :tick-format=\"xFormatter ?? ((v: number) => data[v]?.[index])\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n <VisAxis\n v-if=\"showYAxis\"\n type=\"y\"\n :tick-line=\"false\"\n :tick-format=\"yFormatter\"\n :domain-line=\"false\"\n :grid-line=\"showGridLine\"\n :attributes=\"{\n [Axis.selectors.grid]: {\n class: 'text-muted',\n },\n }\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n </VisXYContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as AreaChart } from './AreaChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,23 @@
{
"name": "chart-bar",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "BarChart.vue",
"content": "<script setup lang=\"ts\">\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { VisAxis, VisGroupedBar, VisStackedBar, VisXYContainer } from '@unovis/vue'\nimport { Axis, GroupedBar, StackedBar } from '@unovis/ts'\nimport { computed, ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n categories: string[]\n index: string\n colors?: string[]\n filterOpacity?: number\n type?: 'stacked' | 'grouped'\n xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n showXAxis?: boolean\n showYAxis?: boolean\n showTooltip?: boolean\n showLegend?: boolean\n showGridLine?: boolean\n}>(), {\n colors: () => defaultColors,\n type: 'grouped',\n filterOpacity: 0.2,\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showLegend: true,\n showGridLine: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({\n name: category,\n color: props.colors[i],\n inactive: false,\n})))\n\nconst isMounted = useMounted()\n\nfunction handleLegendItemClick(d: BulletLegendItemInterface, i: number) {\n // do something when clicked on legend\n}\n\nconst VisBarComponent = computed(() => props.type === 'grouped' ? VisGroupedBar : VisStackedBar)\nconst selectorsBar = computed(() => props.type === 'grouped' ? GroupedBar.selectors.bar : StackedBar.selectors.bar)\n</script>\n\n<template>\n <div :class=\"cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')\">\n <ChartLegend v-if=\"showLegend\" v-model:items=\"legendItems\" @legend-item-click=\"handleLegendItemClick\" />\n\n <VisXYContainer :style=\"{ height: isMounted ? '100%' : 'auto' }\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <ChartCrosshair v-if=\"showTooltip\" :colors=\"colors\" :items=\"legendItems\" :index=\"index\" />\n\n <VisBarComponent\n :x=\"(d: Data, i: number) => i\"\n :y=\"categories.map(category => (d: Data) => d[category]) \"\n :color=\"colors\"\n :rounded-corners=\"4\"\n :bar-padding=\"0.1\"\n :attributes=\"{\n [selectorsBar]: {\n opacity: (d: Data, i:number) => {\n const pos = i % categories.length\n return legendItems[pos]?.inactive ? filterOpacity : 1\n },\n },\n }\"\n />\n\n <VisAxis\n v-if=\"showXAxis\"\n type=\"x\"\n :tick-format=\"xFormatter ?? ((v: number) => data[v]?.[index])\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n <VisAxis\n v-if=\"showYAxis\"\n type=\"y\"\n :tick-line=\"false\"\n :tick-format=\"yFormatter\"\n :domain-line=\"false\"\n :grid-line=\"showGridLine\"\n :attributes=\"{\n [Axis.selectors.grid]: {\n class: 'text-muted',\n },\n }\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n </VisXYContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as BarChart } from './BarChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,23 @@
{
"name": "chart-donut",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "DonutChart.vue",
"content": "<script setup lang=\"ts\">\nimport { VisDonut, VisSingleContainer } from '@unovis/vue'\nimport { Donut } from '@unovis/ts'\nimport { computed, ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartSingleTooltip, defaultColors } from '@/lib/registry/new-york/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n index: string\n category: string\n colors?: string[]\n type?: 'donut' | 'pie'\n filterOpacity?: number\n valueFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n showTooltip?: boolean\n showLegend?: boolean\n}>(), {\n colors: () => defaultColors,\n type: 'donut',\n filterOpacity: 0.2,\n showTooltip: true,\n showLegend: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst isMounted = useMounted()\n\nconst activeSegmentKey = ref<string>()\nconst legendItems = computed(() => props.data.map((item, i) => ({\n name: item[props.index],\n color: props.colors[i],\n inactive: false,\n})))\n</script>\n\n<template>\n <div :class=\"cn('w-full h-48 flex flex-col items-end', $attrs.class ?? '')\">\n <VisSingleContainer :style=\"{ height: isMounted ? '100%' : 'auto' }\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <ChartSingleTooltip :selector=\"Donut.selectors.segment\" :index=\"category\" :items=\"legendItems\" />\n\n <VisDonut\n :value=\"(d: Data) => d[category]\"\n :sort-function=\"(a: Data, b: Data) => (a[category] - b[category])\"\n :color=\"colors\"\n :arc-width=\"type === 'donut' ? 20 : 0\"\n :show-background=\"false\"\n :events=\"{\n [Donut.selectors.segment]: {\n click: (d: any, ev: PointerEvent, i: number, elements: HTMLElement[]) => {\n if (d?.data?.[index] === activeSegmentKey) {\n activeSegmentKey = undefined\n elements.forEach(el => el.style.opacity = '1')\n }\n else {\n activeSegmentKey = d?.data?.[index]\n elements.forEach(el => el.style.opacity = `${filterOpacity}`)\n elements[i].style.opacity = '1'\n }\n },\n },\n }\"\n />\n </VisSingleContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as DonutChart } from './DonutChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,23 @@
{
"name": "chart-line",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "LineChart.vue",
"content": "<script setup lang=\"ts\">\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { VisAxis, VisLine, VisXYContainer } from '@unovis/vue'\nimport { Axis, Line } from '@unovis/ts'\nimport { ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n categories: string[]\n index: string\n colors?: string[]\n filterOpacity?: number\n xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n showXAxis?: boolean\n showYAxis?: boolean\n showTooltip?: boolean\n showLegend?: boolean\n showGridLine?: boolean\n}>(), {\n colors: () => defaultColors,\n filterOpacity: 0.2,\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showLegend: true,\n showGridLine: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({\n name: category,\n color: props.colors[i],\n inactive: false,\n})))\n\nconst isMounted = useMounted()\n\nfunction handleLegendItemClick(d: BulletLegendItemInterface, i: number) {\n // do something when clicked on legend\n}\n</script>\n\n<template>\n <div :class=\"cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')\">\n <ChartLegend v-if=\"showLegend\" v-model:items=\"legendItems\" @legend-item-click=\"handleLegendItemClick\" />\n\n <VisXYContainer\n :margin=\"{ left: 20, right: 20 }\"\n :data=\"data\"\n :style=\"{ height: isMounted ? '100%' : 'auto' }\"\n >\n <ChartCrosshair v-if=\"showTooltip\" :colors=\"colors\" :items=\"legendItems\" :index=\"index\" />\n\n <template v-for=\"(category, i) in categories\" :key=\"category\">\n <VisLine\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n :color=\"colors[i]\"\n :attributes=\"{\n [Line.selectors.line]: {\n opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,\n },\n }\"\n />\n </template>\n\n <VisAxis\n v-if=\"showXAxis\"\n type=\"x\"\n :tick-format=\"xFormatter ?? ((v: number) => data[v]?.[index])\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n <VisAxis\n v-if=\"showYAxis\"\n type=\"y\"\n :tick-line=\"false\"\n :tick-format=\"yFormatter\"\n :domain-line=\"false\"\n :grid-line=\"showGridLine\"\n :attributes=\"{\n [Axis.selectors.grid]: {\n class: 'text-muted',\n },\n }\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n </VisXYContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as LineChart } from './LineChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,35 @@
{
"name": "chart",
"dependencies": [
"@unovis/vue",
"@unovis/ts"
],
"registryDependencies": [
"chart",
"button",
"card"
],
"files": [
{
"name": "ChartCrosshair.vue",
"content": "<script setup lang=\"ts\">\nimport { VisCrosshair, VisTooltip } from '@unovis/vue'\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { omit } from '@unovis/ts'\nimport { createApp } from 'vue'\nimport { ChartTooltip } from '@/lib/registry/default/ui/chart'\n\nconst props = withDefaults(defineProps<{\n colors: string[]\n index: string\n items: BulletLegendItemInterface[]\n}>(), {\n colors: () => [],\n})\n\n// Use weakmap to store reference to each datapoint for Tooltip\nconst wm = new WeakMap()\nfunction template(d: any, ...a: any) {\n if (wm.has(d)) {\n return wm.get(d)\n }\n else {\n const componentDiv = document.createElement('div')\n const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {\n const legendReference = props.items.find(i => i.name === key)\n return { ...legendReference, value }\n })\n createApp(ChartTooltip, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)\n wm.set(d, componentDiv.innerHTML)\n return componentDiv.innerHTML\n }\n}\n</script>\n\n<template>\n <VisTooltip :horizontal-shift=\"20\" :vertical-shift=\"20\" />\n <VisCrosshair :color=\"colors\" :template=\"template\" />\n</template>\n"
},
{
"name": "ChartLegend.vue",
"content": "<script setup lang=\"ts\">\nimport { VisBulletLegend } from '@unovis/vue'\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { BulletLegend } from '@unovis/ts'\nimport { nextTick, onMounted, ref } from 'vue'\nimport { buttonVariants } from '@/lib/registry/default/ui/button'\n\nconst props = withDefaults(defineProps<{ items: BulletLegendItemInterface[] }>(), {\n items: () => [],\n})\n\nconst emits = defineEmits<{\n legendItemClick: [d: BulletLegendItemInterface, i: number]\n 'update:items': [payload: BulletLegendItemInterface[]]\n}>()\n\nconst elRef = ref<HTMLElement>()\n\nonMounted(() => {\n const selector = `.${BulletLegend.selectors.item}`\n nextTick(() => {\n const elements = elRef.value?.querySelectorAll(selector)\n const classes = buttonVariants({ variant: 'ghost', size: 'xs' }).split(' ')\n console.log(elements, classes)\n elements?.forEach(el => el.classList.add(...classes, '!inline-flex', '!mr-2'))\n })\n})\n\nfunction onLegendItemClick(d: BulletLegendItemInterface, i: number) {\n emits('legendItemClick', d, i)\n const isBulletActive = !props.items[i].inactive\n const isFilterApplied = props.items.some(i => i.inactive)\n if (isFilterApplied && isBulletActive) {\n // reset filter\n emits('update:items', props.items.map(item => ({ ...item, inactive: false })))\n }\n else {\n // apply selection, set other item as inactive\n emits('update:items', props.items.map(item => item.name === d.name ? ({ ...d, inactive: false }) : { ...item, inactive: true }))\n }\n}\n</script>\n\n<template>\n <div ref=\"elRef\" class=\"w-max\">\n <VisBulletLegend\n :items=\"items\"\n :on-legend-item-click=\"onLegendItemClick\"\n />\n </div>\n</template>\n"
},
{
"name": "ChartSingleTooltip.vue",
"content": "<script setup lang=\"ts\">\nimport { VisTooltip } from '@unovis/vue'\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { omit } from '@unovis/ts'\nimport { createApp } from 'vue'\nimport { ChartTooltip } from '@/lib/registry/default/ui/chart'\n\nconst props = withDefaults(defineProps<{\n selector: string\n index: string\n items?: BulletLegendItemInterface[]\n valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string\n}>(), {\n valueFormatter: (tick: number) => `${tick}`,\n})\n\n// Use weakmap to store reference to each datapoint for Tooltip\nconst wm = new WeakMap()\nfunction template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {\n if (props.index in d) {\n if (wm.has(d)) {\n return wm.get(d)\n }\n else {\n const componentDiv = document.createElement('div')\n const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {\n const legendReference = props.items?.find(i => i.name === key)\n return { ...legendReference, value: props.valueFormatter(value) }\n })\n createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)\n wm.set(d, componentDiv.innerHTML)\n return componentDiv.innerHTML\n }\n }\n else {\n const data = d.data\n\n if (wm.has(data)) {\n return wm.get(data)\n }\n else {\n const style = getComputedStyle(elements[i])\n const omittedData = [{ name: data.name, value: props.valueFormatter(data[props.index]), color: style.fill }]\n const componentDiv = document.createElement('div')\n createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)\n wm.set(d, componentDiv.innerHTML)\n return componentDiv.innerHTML\n }\n }\n}\n</script>\n\n<template>\n <VisTooltip\n :horizontal-shift=\"20\" :vertical-shift=\"20\" :triggers=\"{\n [selector]: template,\n }\"\n />\n</template>\n"
},
{
"name": "ChartTooltip.vue",
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/default/ui/card'\n\ndefineProps<{\n title?: string\n data: {\n name: string\n color: string\n value: any\n }[]\n}>()\n</script>\n\n<template>\n <Card class=\"text-sm\">\n <CardHeader v-if=\"title\" class=\"p-3 border-b\">\n <CardTitle>\n {{ title }}\n </CardTitle>\n </CardHeader>\n <CardContent class=\"p-3 min-w-[180px] flex flex-col gap-1\">\n <div v-for=\"(item, key) in data\" :key=\"key\" class=\"flex justify-between\">\n <div class=\"flex items-center\">\n <span class=\"w-2.5 h-2.5 mr-2\">\n <svg width=\"100%\" height=\"100%\" viewBox=\"0 0 30 30\">\n <path\n d=\" M 15 15 m -14, 0 a 14,14 0 1,1 28,0 a 14,14 0 1,1 -28,0\"\n :stroke=\"item.color\"\n :fill=\"item.color\"\n stroke-width=\"1\"\n />\n </svg>\n </span>\n <span>{{ item.name }}</span>\n </div>\n <span class=\"font-semibold ml-4\">{{ item.value }}</span>\n </div>\n </CardContent>\n </Card>\n</template>\n"
},
{
"name": "index.ts",
"content": "export { default as ChartTooltip } from './ChartTooltip.vue'\nexport { default as ChartSingleTooltip } from './ChartSingleTooltip.vue'\nexport { default as ChartLegend } from './ChartLegend.vue'\nexport { default as ChartCrosshair } from './ChartCrosshair.vue'\n\nconst COLOR_COUNT = 3\nexport const defaultColors = [\n ...Array.from(Array(COLOR_COUNT).keys()).map(i => `hsl(var(--primary) / ${1 - (1 / COLOR_COUNT) * i})`),\n ...Array.from(Array(COLOR_COUNT).keys()).map(i => `hsl(var(--secondary) / ${1 - (1 / COLOR_COUNT) * i})`),\n]\n"
}
],
"type": "components:ui"
}

View File

@ -11,7 +11,7 @@
}, },
{ {
"name": "index.ts", "name": "index.ts",
"content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'bg-primary text-primary-foreground shadow hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',\n outline:\n 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-9 px-4 py-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-8',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n" "content": "import { cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'bg-primary text-primary-foreground shadow hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',\n outline:\n 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-9 px-4 py-2',\n xs: 'h-7 rounded px-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-8',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n"
} }
], ],
"type": "components:ui" "type": "components:ui"

View File

@ -0,0 +1,23 @@
{
"name": "chart-area",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "AreaChart.vue",
"content": "<script setup lang=\"ts\">\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'\nimport { Area, Axis, Line } from '@unovis/ts'\nimport { ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/new-york/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n categories: string[]\n index: string\n colors?: string[]\n filterOpacity?: number\n xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n showXAxis?: boolean\n showYAxis?: boolean\n showTooltip?: boolean\n showLegend?: boolean\n showGridLine?: boolean\n showGradiant?: boolean\n}>(), {\n colors: () => defaultColors,\n filterOpacity: 0.2,\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showLegend: true,\n showGridLine: true,\n showGradiant: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({\n name: category,\n color: props.colors[i],\n inactive: false,\n})))\n\nconst isMounted = useMounted()\n\nfunction handleLegendItemClick(d: BulletLegendItemInterface, i: number) {\n // do something when clicked on legend\n}\n</script>\n\n<template>\n <div :class=\"cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')\">\n <ChartLegend v-if=\"showLegend\" v-model:items=\"legendItems\" @legend-item-click=\"handleLegendItemClick\" />\n\n <VisXYContainer :style=\"{ height: isMounted ? '100%' : 'auto' }\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <svg width=\"0\" height=\"0\">\n <defs>\n <linearGradient v-for=\"(color, i) in colors\" :id=\"`color-${i}`\" :key=\"i\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <template v-if=\"showGradiant\">\n <stop offset=\"5%\" :stop-color=\"color\" stop-opacity=\"0.4\" />\n <stop offset=\"95%\" :stop-color=\"color\" stop-opacity=\"0\" />\n </template>\n <template v-else>\n <stop offset=\"0%\" :stop-color=\"color\" />\n </template>\n </linearGradient>\n </defs>\n </svg>\n\n <ChartCrosshair v-if=\"showTooltip\" :colors=\"colors\" :items=\"legendItems\" :index=\"index\" />\n\n <template v-for=\"(category, i) in categories\" :key=\"category\">\n <VisArea\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n color=\"auto\"\n :attributes=\"{\n [Area.selectors.area]: {\n fill: `url(#color-${i})`,\n },\n }\"\n :opacity=\"legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1\"\n />\n\n <VisLine\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n :color=\"colors[i]\"\n :attributes=\"{\n [Line.selectors.line]: {\n opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,\n },\n }\"\n />\n </template>\n\n <VisAxis\n v-if=\"showXAxis\"\n type=\"x\"\n :tick-format=\"xFormatter ?? ((v: number) => data[v]?.[index])\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n <VisAxis\n v-if=\"showYAxis\"\n type=\"y\"\n :tick-line=\"false\"\n :tick-format=\"yFormatter\"\n :domain-line=\"false\"\n :grid-line=\"showGridLine\"\n :attributes=\"{\n [Axis.selectors.grid]: {\n class: 'text-muted',\n },\n }\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n </VisXYContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as AreaChart } from './AreaChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,23 @@
{
"name": "chart-bar",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "BarChart.vue",
"content": "<script setup lang=\"ts\">\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { VisAxis, VisGroupedBar, VisStackedBar, VisXYContainer } from '@unovis/vue'\nimport { Axis, GroupedBar, StackedBar } from '@unovis/ts'\nimport { computed, ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/new-york/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n categories: string[]\n index: string\n colors?: string[]\n filterOpacity?: number\n type?: 'stacked' | 'grouped'\n xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n showXAxis?: boolean\n showYAxis?: boolean\n showTooltip?: boolean\n showLegend?: boolean\n showGridLine?: boolean\n}>(), {\n colors: () => defaultColors,\n type: 'grouped',\n filterOpacity: 0.2,\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showLegend: true,\n showGridLine: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({\n name: category,\n color: props.colors[i],\n inactive: false,\n})))\n\nconst isMounted = useMounted()\n\nfunction handleLegendItemClick(d: BulletLegendItemInterface, i: number) {\n // do something when clicked on legend\n}\n\nconst VisBarComponent = computed(() => props.type === 'grouped' ? VisGroupedBar : VisStackedBar)\nconst selectorsBar = computed(() => props.type === 'grouped' ? GroupedBar.selectors.bar : StackedBar.selectors.bar)\n</script>\n\n<template>\n <div :class=\"cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')\">\n <ChartLegend v-if=\"showLegend\" v-model:items=\"legendItems\" @legend-item-click=\"handleLegendItemClick\" />\n\n <VisXYContainer :style=\"{ height: isMounted ? '100%' : 'auto' }\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <ChartCrosshair v-if=\"showTooltip\" :colors=\"colors\" :items=\"legendItems\" :index=\"index\" />\n\n <VisBarComponent\n :x=\"(d: Data, i: number) => i\"\n :y=\"categories.map(category => (d: Data) => d[category]) \"\n :color=\"colors\"\n :rounded-corners=\"4\"\n :bar-padding=\"0.1\"\n :attributes=\"{\n [selectorsBar]: {\n opacity: (d: Data, i:number) => {\n const pos = i % categories.length\n return legendItems[pos]?.inactive ? filterOpacity : 1\n },\n },\n }\"\n />\n\n <VisAxis\n v-if=\"showXAxis\"\n type=\"x\"\n :tick-format=\"xFormatter ?? ((v: number) => data[v]?.[index])\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n <VisAxis\n v-if=\"showYAxis\"\n type=\"y\"\n :tick-line=\"false\"\n :tick-format=\"yFormatter\"\n :domain-line=\"false\"\n :grid-line=\"showGridLine\"\n :attributes=\"{\n [Axis.selectors.grid]: {\n class: 'text-muted',\n },\n }\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n </VisXYContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as BarChart } from './BarChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,23 @@
{
"name": "chart-donut",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "DonutChart.vue",
"content": "<script setup lang=\"ts\">\nimport { VisDonut, VisSingleContainer } from '@unovis/vue'\nimport { Donut } from '@unovis/ts'\nimport { computed, ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartSingleTooltip, defaultColors } from '@/lib/registry/new-york/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n index: string\n category: string\n colors?: string[]\n type?: 'donut' | 'pie'\n filterOpacity?: number\n sortFunction?: (a: any, b: any) => number | undefined\n valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string\n showTooltip?: boolean\n showLegend?: boolean\n}>(), {\n colors: () => defaultColors,\n sortFunction: () => undefined,\n valueFormatter: (tick: number) => `${tick}`,\n type: 'donut',\n filterOpacity: 0.2,\n showTooltip: true,\n showLegend: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst isMounted = useMounted()\n\nconst activeSegmentKey = ref<string>()\nconst legendItems = computed(() => props.data.map((item, i) => ({\n name: item[props.index],\n color: props.colors[i],\n inactive: false,\n})))\n\nconst totalValue = computed(() => props.data.reduce((prev, curr) => {\n return prev + curr[props.category]\n}, 0))\n</script>\n\n<template>\n <div :class=\"cn('w-full h-48 flex flex-col items-end', $attrs.class ?? '')\">\n <VisSingleContainer :style=\"{ height: isMounted ? '100%' : 'auto' }\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <ChartSingleTooltip\n :selector=\"Donut.selectors.segment\"\n :index=\"category\"\n :items=\"legendItems\"\n :value-formatter=\"valueFormatter\"\n />\n\n <VisDonut\n :value=\"(d: Data) => d[category]\"\n :sort-function=\"sortFunction\"\n :color=\"colors\"\n :arc-width=\"type === 'donut' ? 20 : 0\"\n :show-background=\"false\"\n :central-label=\"valueFormatter(totalValue)\"\n :events=\"{\n [Donut.selectors.segment]: {\n click: (d: any, ev: PointerEvent, i: number, elements: HTMLElement[]) => {\n if (d?.data?.[index] === activeSegmentKey) {\n activeSegmentKey = undefined\n elements.forEach(el => el.style.opacity = '1')\n }\n else {\n activeSegmentKey = d?.data?.[index]\n elements.forEach(el => el.style.opacity = `${filterOpacity}`)\n elements[i].style.opacity = '1'\n }\n },\n },\n }\"\n />\n </VisSingleContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as DonutChart } from './DonutChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,23 @@
{
"name": "chart-line",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "LineChart.vue",
"content": "<script setup lang=\"ts\">\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { VisAxis, VisLine, VisXYContainer } from '@unovis/vue'\nimport { Axis, Line } from '@unovis/ts'\nimport { ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/new-york/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<{\n data: any[]\n categories: string[]\n index: string\n colors?: string[]\n filterOpacity?: number\n xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string\n showXAxis?: boolean\n showYAxis?: boolean\n showTooltip?: boolean\n showLegend?: boolean\n showGridLine?: boolean\n}>(), {\n colors: () => defaultColors,\n filterOpacity: 0.2,\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showLegend: true,\n showGridLine: true,\n})\n\ntype Data = typeof props.data[number]\n\nconst legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({\n name: category,\n color: props.colors[i],\n inactive: false,\n})))\n\nconst isMounted = useMounted()\n\nfunction handleLegendItemClick(d: BulletLegendItemInterface, i: number) {\n // do something when clicked on legend\n}\n</script>\n\n<template>\n <div :class=\"cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')\">\n <ChartLegend v-if=\"showLegend\" v-model:items=\"legendItems\" @legend-item-click=\"handleLegendItemClick\" />\n\n <VisXYContainer\n :margin=\"{ left: 20, right: 20 }\"\n :data=\"data\"\n :style=\"{ height: isMounted ? '100%' : 'auto' }\"\n >\n <ChartCrosshair v-if=\"showTooltip\" :colors=\"colors\" :items=\"legendItems\" :index=\"index\" />\n\n <template v-for=\"(category, i) in categories\" :key=\"category\">\n <VisLine\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n :color=\"colors[i]\"\n :attributes=\"{\n [Line.selectors.line]: {\n opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,\n },\n }\"\n />\n </template>\n\n <VisAxis\n v-if=\"showXAxis\"\n type=\"x\"\n :tick-format=\"xFormatter ?? ((v: number) => data[v]?.[index])\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n <VisAxis\n v-if=\"showYAxis\"\n type=\"y\"\n :tick-line=\"false\"\n :tick-format=\"yFormatter\"\n :domain-line=\"false\"\n :grid-line=\"showGridLine\"\n :attributes=\"{\n [Axis.selectors.grid]: {\n class: 'text-muted',\n },\n }\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n </VisXYContainer>\n </div>\n</template>\n\n<style>\n:root {\n --vis-tooltip-background-color: none;\n --vis-tooltip-border-color: none;\n --vis-tooltip-text-color: none;\n --vis-tooltip-shadow-color: none;\n --vis-tooltip-backdrop-filter: none;\n --vis-tooltip-padding: none;\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as LineChart } from './LineChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -0,0 +1,35 @@
{
"name": "chart",
"dependencies": [
"@unovis/vue",
"@unovis/ts"
],
"registryDependencies": [
"chart",
"button",
"card"
],
"files": [
{
"name": "ChartCrosshair.vue",
"content": "<script setup lang=\"ts\">\nimport { VisCrosshair, VisTooltip } from '@unovis/vue'\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { omit } from '@unovis/ts'\nimport { createApp } from 'vue'\nimport { ChartTooltip } from '@/lib/registry/new-york/ui/chart'\n\nconst props = withDefaults(defineProps<{\n colors: string[]\n index: string\n items: BulletLegendItemInterface[]\n}>(), {\n colors: () => [],\n})\n\n// Use weakmap to store reference to each datapoint for Tooltip\nconst wm = new WeakMap()\nfunction template(d: any, ...a: any) {\n if (wm.has(d)) {\n return wm.get(d)\n }\n else {\n const componentDiv = document.createElement('div')\n const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {\n const legendReference = props.items.find(i => i.name === key)\n return { ...legendReference, value }\n })\n createApp(ChartTooltip, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)\n wm.set(d, componentDiv.innerHTML)\n return componentDiv.innerHTML\n }\n}\n</script>\n\n<template>\n <VisTooltip :horizontal-shift=\"20\" :vertical-shift=\"20\" />\n <VisCrosshair :color=\"colors\" :template=\"template\" />\n</template>\n"
},
{
"name": "ChartLegend.vue",
"content": "<script setup lang=\"ts\">\nimport { VisBulletLegend } from '@unovis/vue'\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { BulletLegend } from '@unovis/ts'\nimport { nextTick, onMounted, ref } from 'vue'\nimport { buttonVariants } from '@/lib/registry/new-york/ui/button'\n\nconst props = withDefaults(defineProps<{ items: BulletLegendItemInterface[] }>(), {\n items: () => [],\n})\n\nconst emits = defineEmits<{\n legendItemClick: [d: BulletLegendItemInterface, i: number]\n 'update:items': [payload: BulletLegendItemInterface[]]\n}>()\n\nconst elRef = ref<HTMLElement>()\n\nonMounted(() => {\n const selector = `.${BulletLegend.selectors.item}`\n nextTick(() => {\n const elements = elRef.value?.querySelectorAll(selector)\n const classes = buttonVariants({ variant: 'ghost', size: 'xs' }).split(' ')\n\n elements?.forEach(el => el.classList.add(...classes, '!inline-flex', '!mr-2'))\n })\n})\n\nfunction onLegendItemClick(d: BulletLegendItemInterface, i: number) {\n emits('legendItemClick', d, i)\n const isBulletActive = !props.items[i].inactive\n const isFilterApplied = props.items.some(i => i.inactive)\n if (isFilterApplied && isBulletActive) {\n // reset filter\n emits('update:items', props.items.map(item => ({ ...item, inactive: false })))\n }\n else {\n // apply selection, set other item as inactive\n emits('update:items', props.items.map(item => item.name === d.name ? ({ ...d, inactive: false }) : { ...item, inactive: true }))\n }\n}\n</script>\n\n<template>\n <div ref=\"elRef\" class=\"w-max\">\n <VisBulletLegend\n :items=\"items\"\n :on-legend-item-click=\"onLegendItemClick\"\n />\n </div>\n</template>\n"
},
{
"name": "ChartSingleTooltip.vue",
"content": "<script setup lang=\"ts\">\nimport { VisTooltip } from '@unovis/vue'\nimport type { BulletLegendItemInterface } from '@unovis/ts'\nimport { omit } from '@unovis/ts'\nimport { createApp } from 'vue'\nimport { ChartTooltip } from '@/lib/registry/new-york/ui/chart'\n\nconst props = withDefaults(defineProps<{\n selector: string\n index: string\n items?: BulletLegendItemInterface[]\n valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string\n}>(), {\n valueFormatter: (tick: number) => `${tick}`,\n})\n\n// Use weakmap to store reference to each datapoint for Tooltip\nconst wm = new WeakMap()\nfunction template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {\n if (props.index in d) {\n if (wm.has(d)) {\n return wm.get(d)\n }\n else {\n const componentDiv = document.createElement('div')\n const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {\n const legendReference = props.items?.find(i => i.name === key)\n return { ...legendReference, value: props.valueFormatter(value) }\n })\n createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)\n wm.set(d, componentDiv.innerHTML)\n return componentDiv.innerHTML\n }\n }\n else {\n const data = d.data\n\n if (wm.has(data)) {\n return wm.get(data)\n }\n else {\n const style = getComputedStyle(elements[i])\n const omittedData = [{ name: data.name, value: props.valueFormatter(data[props.index]), color: style.fill }]\n const componentDiv = document.createElement('div')\n createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)\n wm.set(d, componentDiv.innerHTML)\n return componentDiv.innerHTML\n }\n }\n}\n</script>\n\n<template>\n <VisTooltip\n :horizontal-shift=\"20\" :vertical-shift=\"20\" :triggers=\"{\n [selector]: template,\n }\"\n />\n</template>\n"
},
{
"name": "ChartTooltip.vue",
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/new-york/ui/card'\n\ndefineProps<{\n title?: string\n data: {\n name: string\n color: string\n value: any\n }[]\n}>()\n</script>\n\n<template>\n <Card class=\"text-sm\">\n <CardHeader v-if=\"title\" class=\"p-3 border-b\">\n <CardTitle>\n {{ title }}\n </CardTitle>\n </CardHeader>\n <CardContent class=\"p-3 min-w-[180px] flex flex-col gap-1\">\n <div v-for=\"(item, key) in data\" :key=\"key\" class=\"flex justify-between\">\n <div class=\"flex items-center\">\n <span class=\"w-2.5 h-2.5 mr-2\">\n <svg width=\"100%\" height=\"100%\" viewBox=\"0 0 30 30\">\n <path\n d=\" M 15 15 m -14, 0 a 14,14 0 1,1 28,0 a 14,14 0 1,1 -28,0\"\n :stroke=\"item.color\"\n :fill=\"item.color\"\n stroke-width=\"1\"\n />\n </svg>\n </span>\n <span>{{ item.name }}</span>\n </div>\n <span class=\"font-semibold ml-4\">{{ item.value }}</span>\n </div>\n </CardContent>\n </Card>\n</template>\n"
},
{
"name": "index.ts",
"content": "export { default as ChartTooltip } from './ChartTooltip.vue'\nexport { default as ChartSingleTooltip } from './ChartSingleTooltip.vue'\nexport { default as ChartLegend } from './ChartLegend.vue'\nexport { default as ChartCrosshair } from './ChartCrosshair.vue'\n\nconst COLOR_COUNT = 3\nexport const defaultColors = [\n ...Array.from(Array(COLOR_COUNT).keys()).map(i => `hsl(var(--primary) / ${1 - (1 / COLOR_COUNT) * i})`),\n ...Array.from(Array(COLOR_COUNT).keys()).map(i => `hsl(var(--secondary) / ${1 - (1 / COLOR_COUNT) * i})`),\n]\n"
}
],
"type": "components:ui"
}