feat: add new-york style components

This commit is contained in:
Ahmed 2023-09-06 14:04:50 +01:00
parent 5a96372142
commit 6ff3074bc0
48 changed files with 764 additions and 0 deletions

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import {
AccordionRoot,
type AccordionRootEmits,
type AccordionRootProps,
} from 'radix-vue'
import { useEmitAsProps } from '@/lib/utils'
const props = defineProps<AccordionRootProps>()
const emits = defineEmits<AccordionRootEmits>()
</script>
<template>
<AccordionRoot v-bind="{ ...props, ...useEmitAsProps(emits) }" class="accordion">
<slot />
</AccordionRoot>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { AccordionContent, type AccordionContentProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionContentProps & { class?: string }>()
</script>
<template>
<AccordionContent
v-bind="props"
:class="
cn(
'overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
props.class,
)
"
>
<div class="pb-4 pt-0">
<slot />
</div>
</AccordionContent>
</template>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { AccordionItem, type AccordionItemProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionItemProps & { class?: string }>()
</script>
<template>
<AccordionItem
v-bind="props"
:class="cn('border-b', props.class ?? '')"
>
<slot />
</AccordionItem>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import {
AccordionHeader,
AccordionTrigger,
type AccordionTriggerProps,
} from 'radix-vue'
import RadixIconsChevronDown from '~icons/radix-icons/chevron-down'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionTriggerProps & { class?: string }>()
</script>
<template>
<AccordionHeader class="flex" as="div">
<AccordionTrigger
v-bind="props"
:class="
cn(
'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
props.class,
)
"
>
<slot />
<RadixIconsChevronDown
class="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"
/>
</AccordionTrigger>
</AccordionHeader>
</template>

View File

@ -0,0 +1,4 @@
export { default as Accordion } from './Accordion.vue'
export { default as AccordionContent } from './AccordionContent.vue'
export { default as AccordionItem } from './AccordionItem.vue'
export { default as AccordionTrigger } from './AccordionTrigger.vue'

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { badgeVariants } from '.'
import { cn } from '@/lib/utils'
interface BadgeVariantProps extends VariantProps<typeof badgeVariants> {}
interface Props {
variant?: BadgeVariantProps['variant']
}
defineProps<Props>()
</script>
<template>
<div :class="cn(badgeVariants({ variant }), $attrs.class ?? '')">
<slot />
</div>
</template>

View File

@ -0,0 +1,23 @@
import { cva } from 'class-variance-authority'
export { default as Badge } from './Badge.vue'
export const badgeVariants = cva(
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)

View File

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

View File

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

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<div
:class="
cn(
'rounded-xl border bg-card text-card-foreground shadow',
props.class,
)
"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<div :class="cn('p-6 pt-0', props.class)">
<slot />
</div>
</template>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<p :class="cn('text-sm text-muted-foreground', props.class)">
<slot />
</p>
</template>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<div :class="cn('flex items-center p-6 pt-0', props.class)">
<slot />
</div>
</template>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<div :class="cn('flex flex-col space-y-1.5 p-6', props.class)">
<slot />
</div>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<h3
:class="
cn('font-semibold leading-none tracking-tight', props.class)
"
>
<slot />
</h3>
</template>

View File

@ -0,0 +1,6 @@
export { default as Card } from './Card.vue'
export { default as CardHeader } from './CardHeader.vue'
export { default as CardTitle } from './CardTitle.vue'
export { default as CardDescription } from './CardDescription.vue'
export { default as CardContent } from './CardContent.vue'
export { default as CardFooter } from './CardFooter.vue'

View File

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

View File

@ -0,0 +1 @@
export { default as Input } from './Input.vue'

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import {
ProgressIndicator,
ProgressRoot,
type ProgressRootProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<ProgressRootProps & { class?: string }>(),
{
class: '',
modelValue: 0,
},
)
</script>
<template>
<ProgressRoot
:class="
cn(
'relative h-2 w-full overflow-hidden rounded-full bg-primary/20',
props.class,
)
"
v-bind="props"
>
<ProgressIndicator
:class="
cn(
'h-full w-full flex-1 bg-primary transition-all',
props.class,
)
"
:style="`transform: translateX(-${100 - (props.modelValue ?? 0)}%);`"
/>
</ProgressRoot>
</template>

View File

@ -0,0 +1 @@
export { default as Progress } from './Progress.vue'

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import type { SliderRootEmits, SliderRootProps } from 'radix-vue'
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<SliderRootProps>()
const emits = defineEmits<SliderRootEmits>()
</script>
<template>
<SliderRoot
:class="cn(
'relative flex w-full touch-none select-none items-center',
$attrs.class ?? '',
)"
v-bind="{ ...props, ...useEmitAsProps(emits) }"
>
<SliderTrack class="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderRange class="absolute h-full bg-primary" />
</SliderTrack>
<SliderThumb class="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</SliderRoot>
</template>

View File

@ -0,0 +1 @@
export { default as Slider } from './Slider.vue'

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import {
SwitchRoot,
type SwitchRootEmits,
type SwitchRootProps,
SwitchThumb,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<SwitchRootProps & { class?: string }>()
const emits = defineEmits<SwitchRootEmits>()
</script>
<template>
<SwitchRoot
v-bind="props"
:class="
cn(
'peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
props.class,
)
"
@update:checked="emits('update:checked', $event)"
>
<SwitchThumb
:class="
cn(
'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
)
"
/>
</SwitchRoot>
</template>

View File

@ -0,0 +1 @@
export { default as Switch } from './Switch.vue'

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<div class="w-full overflow-auto">
<table :class="cn('w-full caption-bottom text-sm', props.class)">
<slot />
</table>
</div>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<tbody :class="cn('[&_tr:last-child]:border-0', props.class)">
<slot />
</tbody>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<caption :class="cn('mt-4 text-sm text-muted-foreground', props.class)">
<slot />
</caption>
</template>

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<td
:class="
cn(
'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
props.class,
)
"
>
<slot />
</td>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import TableRow from './TableRow.vue'
import TableCell from './TableCell.vue'
import { cn } from '@/lib/utils'
interface Props {
class?: string
colspan?: number
}
const props = withDefaults(defineProps<Props>(), {
class: '',
colspan: 1,
})
</script>
<template>
<TableRow>
<TableCell
:class="
cn(
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
props.class,
)
"
:colspan="props.colspan"
>
<div class="flex items-center justify-center py-10">
<slot />
</div>
</TableCell>
</TableRow>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<tfoot :class="cn('bg-primary font-medium text-primary-foreground', props.class)">
<slot />
</tfoot>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<th :class="cn('h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', props.class)">
<slot />
</th>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<thead :class="cn('[&_tr]:border-b', props.class)">
<slot />
</thead>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: string }>()
</script>
<template>
<tr :class="cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', props.class)">
<slot />
</tr>
</template>

View File

@ -0,0 +1,8 @@
export { default as Table } from './Table.vue'
export { default as TableBody } from './TableBody.vue'
export { default as TableCell } from './TableCell.vue'
export { default as TableHead } from './TableHead.vue'
export { default as TableHeader } from './TableHeader.vue'
export { default as TableRow } from './TableRow.vue'
export { default as TableCaption } from './TableCaption.vue'
export { default as TableEmpty } from './TableEmpty.vue'

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { TabsRoot, type TabsRootProps } from 'radix-vue'
const props = defineProps<TabsRootProps>()
</script>
<template>
<TabsRoot v-bind="props">
<slot />
</TabsRoot>
</template>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { TabsContent, type TabsContentProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<TabsContentProps & { class?: string }>()
</script>
<template>
<TabsContent
:class="cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', props.class)"
v-bind="props"
>
<slot />
</TabsContent>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { TabsList, type TabsListProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<TabsListProps & { class?: string }>()
</script>
<template>
<TabsList
v-bind="props"
:class="
cn(
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
props.class,
)
"
>
<slot />
</TabsList>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { TabsTrigger, type TabsTriggerProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<TabsTriggerProps & { class?: string }>()
</script>
<template>
<TabsTrigger
v-bind="props"
:class="
cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
props.class,
)
"
>
<slot />
</TabsTrigger>
</template>

View File

@ -0,0 +1,4 @@
export { default as Tabs } from './Tabs.vue'
export { default as TabsTrigger } from './TabsTrigger.vue'
export { default as TabsList } from './TabsList.vue'
export { default as TabsContent } from './TabsContent.vue'

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
import { cn } from '@/lib/utils'
const props = defineProps<{
defaultValue?: string | number
modelValue?: string | number
}>()
const emits = defineEmits<{
(e: 'update:modelValue', payload: string | number): void
}>()
const modelValue = useVModel(props, 'modelValue', emits, {
passive: true,
defaultValue: props.defaultValue,
})
</script>
<template>
<textarea v-model="modelValue" :class="cn('flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', $attrs.class ?? '')" />
</template>

View File

@ -0,0 +1 @@
export { default as Textarea } from './Textarea.vue'

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import type { ToggleEmits, ToggleProps } from 'radix-vue'
import { Toggle } from 'radix-vue'
import type { VariantProps } from 'class-variance-authority'
import { computed } from 'vue'
import { toggleVariants } from '.'
import { cn, useEmitAsProps } from '@/lib/utils'
interface ToggleVariantProps extends VariantProps<typeof toggleVariants> {}
interface Props extends ToggleProps {
variant?: ToggleVariantProps['variant']
size?: ToggleVariantProps['size']
}
const props = withDefaults(defineProps<Props>(), {
variant: 'default',
size: 'default',
})
const emits = defineEmits<ToggleEmits>()
const toggleProps = computed(() => {
// eslint-disable-next-line unused-imports/no-unused-vars
const { variant, size, ...otherProps } = props
return otherProps
})
</script>
<template>
<Toggle
v-bind="{ ...toggleProps, ...useEmitAsProps(emits) }"
:class="cn(toggleVariants({ variant, size, class: $attrs.class ?? '' }))"
>
<slot />
</Toggle>
</template>

View File

@ -0,0 +1,25 @@
import { cva } from 'class-variance-authority'
export { default as Toggle } from './Toggle.vue'
export const toggleVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
{
variants: {
variant: {
default: 'bg-transparent',
outline:
'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-9 px-3',
sm: 'h-8 px-2',
lg: 'h-10 px-3',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { TooltipRoot, type TooltipRootEmits, type TooltipRootProps } from 'radix-vue'
import { useEmitAsProps } from '@/lib/utils'
const props = defineProps<TooltipRootProps>()
const emits = defineEmits<TooltipRootEmits>()
</script>
<template>
<TooltipRoot v-bind="{ ...props, ...useEmitAsProps(emits) }">
<slot />
</TooltipRoot>
</template>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { TooltipContent, type TooltipContentEmits, type TooltipContentProps } from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = withDefaults(defineProps<TooltipContentProps>(), {
sideOffset: 4,
})
const emits = defineEmits<TooltipContentEmits>()
</script>
<template>
<TooltipContent v-bind="{ ...props, ...useEmitAsProps(emits) }" :class="cn('z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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', $attrs.class ?? '')">
<slot />
</TooltipContent>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { TooltipProvider, type TooltipProviderProps } from 'radix-vue'
const props = defineProps<TooltipProviderProps>()
</script>
<template>
<TooltipProvider v-bind="props">
<slot />
</TooltipProvider>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { TooltipTrigger, type TooltipTriggerProps } from 'radix-vue'
const props = defineProps<TooltipTriggerProps>()
</script>
<template>
<TooltipTrigger v-bind="props">
<slot />
</TooltipTrigger>
</template>

View File

@ -0,0 +1,4 @@
export { default as Tooltip } from './Tooltip.vue'
export { default as TooltipContent } from './TooltipContent.vue'
export { default as TooltipTrigger } from './TooltipTrigger.vue'
export { default as TooltipProvider } from './TooltipProvider.vue'