update Button and ButtonGroup component. Start separating "crm-" named components for future updates
This commit is contained in:
@@ -1,17 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from 'vue'
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
import { cn } from '@/lib/utils'
|
import type { HTMLAttributes } from "vue"
|
||||||
import { Primitive, type PrimitiveProps } from 'reka-ui'
|
import type { ButtonVariants } from "."
|
||||||
import { type ButtonVariants, buttonVariants } from '.'
|
import { Primitive } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "."
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
interface Props extends PrimitiveProps {
|
||||||
variant?: ButtonVariants['variant']
|
variant?: ButtonVariants["variant"]
|
||||||
size?: ButtonVariants['size']
|
size?: ButtonVariants["size"]
|
||||||
class?: HTMLAttributes['class']
|
class?: HTMLAttributes["class"]
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
as: 'button',
|
as: "button",
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,38 @@
|
|||||||
import { cva, type VariantProps } from 'class-variance-authority'
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
export { default as Button } from './Button.vue'
|
export { default as Button } from "./Button.vue"
|
||||||
|
|
||||||
export const buttonVariants = cva(
|
export const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium shadow-sm active:shadow-none active:inset-shadow-2xs active:inset-shadow-black/15 active:border-none transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
'bg-slate-100 hover:bg-slate-200 dark:bg-neutral-700 dark:hover:bg-neutral-700/66 border-b-1 border-t-1 border-t-white/75 border-b-slate-300 dark:border-t-neutral-600 dark:border-b-neutral-800',
|
"bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
action:
|
|
||||||
'bg-blue-600 border-b-1 border-t-1 border-t-blue-400 border-b-blue-800 active:bg-blue-700 hover:bg-blue-500 text-white',
|
|
||||||
destructive:
|
destructive:
|
||||||
'bg-destructive dark:bg-red-700 text-white border-b-1 border-t-1 border-t-red-200 border-b-red-700 dark:border-t-red-400 dark:border-b-red-800 hover:bg-red-600 active:bg-red-500 active:inset-shadow-red-950 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
success:
|
|
||||||
'bg-success text-success-foreground border-b-1 border-t-1 border-t-lime-200 border-b-lime-500 dark:border-t-lime-400 dark:border-b-lime-800 hover:bg-lime-500 active:bg-lime-500 active:inset-shadow-lime-600 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
|
|
||||||
outline:
|
outline:
|
||||||
'border bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
secondary:
|
secondary:
|
||||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost:
|
ghost:
|
||||||
'border-none shadow-none hover:bg-slate-100 hover:text-accent-foreground dark:hover:bg-accent/50',
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: 'text-primary underline-offset-4 hover:underline',
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
pressed:
|
|
||||||
'bg-slate-100 dark:bg-neutral-700 shadow-none inset-shadow-2xs inset-shadow-black/15 border-none',
|
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: 'h-8 px-6 active:pt-[1px] has-[>svg]:px-3',
|
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
sm: 'h-7 px-4 rounded-md gap-1.5 has-[>svg]:px-2.5',
|
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
icon: 'size-9',
|
"icon": "size-9",
|
||||||
|
"icon-sm": "size-8",
|
||||||
|
"icon-lg": "size-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'default',
|
variant: "default",
|
||||||
size: 'default',
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Primitive, type PrimitiveProps } from 'reka-ui'
|
||||||
|
import { type ButtonVariants, buttonVariants } from '.'
|
||||||
|
|
||||||
|
interface Props extends PrimitiveProps {
|
||||||
|
variant?: ButtonVariants['variant']
|
||||||
|
size?: ButtonVariants['size']
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
as: 'button',
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Primitive
|
||||||
|
data-slot="button"
|
||||||
|
:as="as"
|
||||||
|
:as-child="asChild"
|
||||||
|
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Primitive>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
export { default as Button } from './Button.vue'
|
||||||
|
|
||||||
|
export const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium shadow-sm active:shadow-none active:inset-shadow-2xs active:inset-shadow-black/15 active:border-none transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
'bg-slate-100 hover:bg-slate-200 dark:bg-neutral-700 dark:hover:bg-neutral-700/66 border-b-1 border-t-1 border-t-white/75 border-b-slate-300 dark:border-t-neutral-600 dark:border-b-neutral-800',
|
||||||
|
action:
|
||||||
|
'bg-blue-600 border-b-1 border-t-1 border-t-blue-400 border-b-blue-800 active:bg-blue-700 hover:bg-blue-500 text-white',
|
||||||
|
destructive:
|
||||||
|
'bg-destructive dark:bg-red-700 text-white border-b-1 border-t-1 border-t-red-200 border-b-red-700 dark:border-t-red-400 dark:border-b-red-800 hover:bg-red-600 active:bg-red-500 active:inset-shadow-red-950 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
|
||||||
|
success:
|
||||||
|
'bg-success text-success-foreground border-b-1 border-t-1 border-t-lime-200 border-b-lime-500 dark:border-t-lime-400 dark:border-b-lime-800 hover:bg-lime-500 active:bg-lime-500 active:inset-shadow-lime-600 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
|
||||||
|
outline:
|
||||||
|
'border bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||||
|
secondary:
|
||||||
|
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||||
|
ghost:
|
||||||
|
'border-none shadow-none hover:bg-slate-100 hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||||
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
|
pressed:
|
||||||
|
'bg-slate-100 dark:bg-neutral-700 shadow-none inset-shadow-2xs inset-shadow-black/15 border-none',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-8 px-6 active:pt-[1px] has-[>svg]:px-3',
|
||||||
|
sm: 'h-7 px-4 rounded-md gap-1.5 has-[>svg]:px-2.5',
|
||||||
|
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||||
|
icon: 'size-9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="input-group"
|
||||||
|
role="group"
|
||||||
|
:class="cn(
|
||||||
|
'group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border outline-none',
|
||||||
|
'h-10 min-w-0 has-[>textarea]:h-auto',
|
||||||
|
|
||||||
|
// Variants based on alignment.
|
||||||
|
'has-[>[data-align=inline-start]]:[&>input]:pl-2',
|
||||||
|
'has-[>[data-align=inline-end]]:[&>input]:pr-2',
|
||||||
|
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
|
||||||
|
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
|
||||||
|
|
||||||
|
// Focus state.
|
||||||
|
'has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-offset-2 has-[[data-slot=input-group-control]:focus-visible]:ring-offset-background',
|
||||||
|
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { InputGroupVariants } from "."
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { inputGroupAddonVariants } from "."
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
align?: InputGroupVariants["align"]
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>(), {
|
||||||
|
align: "inline-start",
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleInputGroupAddonClick(e: MouseEvent) {
|
||||||
|
const currentTarget = e.currentTarget as HTMLElement | null
|
||||||
|
const target = e.target as HTMLElement | null
|
||||||
|
if (target && target.closest("button")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (currentTarget && currentTarget?.parentElement) {
|
||||||
|
currentTarget.parentElement?.querySelector("input")?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
role="group"
|
||||||
|
data-slot="input-group-addon"
|
||||||
|
:data-align="props.align"
|
||||||
|
:class="cn(inputGroupAddonVariants({ align: props.align }), props.class)"
|
||||||
|
@click="handleInputGroupAddonClick"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { InputGroupButtonProps } from "."
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from '@/components/ui/crm-button'
|
||||||
|
import { inputGroupButtonVariants } from "."
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<InputGroupButtonProps>(), {
|
||||||
|
size: "xs",
|
||||||
|
variant: "ghost",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
:data-size="props.size"
|
||||||
|
:variant="props.variant"
|
||||||
|
:class="cn(inputGroupButtonVariants({ size: props.size }), props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Input } from '@/components/ui/crm-input'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Input
|
||||||
|
data-slot="input-group-control"
|
||||||
|
:class="cn(
|
||||||
|
'flex-1 border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent ring-offset-transparent dark:bg-transparent',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
:class="cn(
|
||||||
|
'text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Textarea
|
||||||
|
data-slot="input-group-control"
|
||||||
|
:class="cn(
|
||||||
|
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 focus-visible:ring-transparent ring-offset-transparent dark:bg-transparent',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { ButtonVariants } from '@/components/ui/crm-button'
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
export { default as InputGroup } from "./InputGroup.vue"
|
||||||
|
export { default as InputGroupAddon } from "./InputGroupAddon.vue"
|
||||||
|
export { default as InputGroupButton } from "./InputGroupButton.vue"
|
||||||
|
export { default as InputGroupInput } from "./InputGroupInput.vue"
|
||||||
|
export { default as InputGroupText } from "./InputGroupText.vue"
|
||||||
|
export { default as InputGroupTextarea } from "./InputGroupTextarea.vue"
|
||||||
|
|
||||||
|
export const inputGroupAddonVariants = cva(
|
||||||
|
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
align: {
|
||||||
|
"inline-start":
|
||||||
|
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
||||||
|
"inline-end":
|
||||||
|
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
||||||
|
"block-start":
|
||||||
|
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
|
||||||
|
"block-end":
|
||||||
|
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
align: "inline-start",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type InputGroupVariants = VariantProps<typeof inputGroupAddonVariants>
|
||||||
|
|
||||||
|
export const inputGroupButtonVariants = cva(
|
||||||
|
"text-sm shadow-none flex gap-2 items-center",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
"xs": "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
||||||
|
"sm": "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
|
||||||
|
"icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
||||||
|
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: "xs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type InputGroupButtonVariants = VariantProps<typeof inputGroupButtonVariants>
|
||||||
|
|
||||||
|
export interface InputGroupButtonProps {
|
||||||
|
variant?: ButtonVariants["variant"]
|
||||||
|
size?: InputGroupButtonVariants["size"]
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
defaultValue?: string | number
|
||||||
|
modelValue?: string | number
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
|
||||||
|
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"
|
||||||
|
data-slot="input"
|
||||||
|
:class="cn(
|
||||||
|
'file:text-foreground placeholder:text-muted-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-input px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||||
|
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||||
|
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as Input } from "./Input.vue"
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="input-group"
|
||||||
|
role="group"
|
||||||
|
:class="cn(
|
||||||
|
'group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border outline-none',
|
||||||
|
'h-10 min-w-0 has-[>textarea]:h-auto',
|
||||||
|
|
||||||
|
// Variants based on alignment.
|
||||||
|
'has-[>[data-align=inline-start]]:[&>input]:pl-2',
|
||||||
|
'has-[>[data-align=inline-end]]:[&>input]:pr-2',
|
||||||
|
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
|
||||||
|
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
|
||||||
|
|
||||||
|
// Focus state.
|
||||||
|
'has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-offset-2 has-[[data-slot=input-group-control]:focus-visible]:ring-offset-background',
|
||||||
|
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { InputGroupVariants } from "."
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { inputGroupAddonVariants } from "."
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
align?: InputGroupVariants["align"]
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>(), {
|
||||||
|
align: "inline-start",
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleInputGroupAddonClick(e: MouseEvent) {
|
||||||
|
const currentTarget = e.currentTarget as HTMLElement | null
|
||||||
|
const target = e.target as HTMLElement | null
|
||||||
|
if (target && target.closest("button")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (currentTarget && currentTarget?.parentElement) {
|
||||||
|
currentTarget.parentElement?.querySelector("input")?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
role="group"
|
||||||
|
data-slot="input-group-addon"
|
||||||
|
:data-align="props.align"
|
||||||
|
:class="cn(inputGroupAddonVariants({ align: props.align }), props.class)"
|
||||||
|
@click="handleInputGroupAddonClick"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { InputGroupButtonProps } from "."
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { inputGroupButtonVariants } from "."
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<InputGroupButtonProps>(), {
|
||||||
|
size: "xs",
|
||||||
|
variant: "ghost",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
:data-size="props.size"
|
||||||
|
:variant="props.variant"
|
||||||
|
:class="cn(inputGroupButtonVariants({ size: props.size }), props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Input
|
||||||
|
data-slot="input-group-control"
|
||||||
|
:class="cn(
|
||||||
|
'flex-1 rounded-none border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent ring-offset-transparent dark:bg-transparent',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
:class="cn(
|
||||||
|
'text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Textarea
|
||||||
|
data-slot="input-group-control"
|
||||||
|
:class="cn(
|
||||||
|
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 focus-visible:ring-transparent ring-offset-transparent dark:bg-transparent',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { ButtonVariants } from '@/components/ui/button'
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
export { default as InputGroup } from "./InputGroup.vue"
|
||||||
|
export { default as InputGroupAddon } from "./InputGroupAddon.vue"
|
||||||
|
export { default as InputGroupButton } from "./InputGroupButton.vue"
|
||||||
|
export { default as InputGroupInput } from "./InputGroupInput.vue"
|
||||||
|
export { default as InputGroupText } from "./InputGroupText.vue"
|
||||||
|
export { default as InputGroupTextarea } from "./InputGroupTextarea.vue"
|
||||||
|
|
||||||
|
export const inputGroupAddonVariants = cva(
|
||||||
|
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
align: {
|
||||||
|
"inline-start":
|
||||||
|
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
||||||
|
"inline-end":
|
||||||
|
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
||||||
|
"block-start":
|
||||||
|
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
|
||||||
|
"block-end":
|
||||||
|
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
align: "inline-start",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type InputGroupVariants = VariantProps<typeof inputGroupAddonVariants>
|
||||||
|
|
||||||
|
export const inputGroupButtonVariants = cva(
|
||||||
|
"text-sm shadow-none flex gap-2 items-center",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
"xs": "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
||||||
|
"sm": "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
|
||||||
|
"icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
||||||
|
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: "xs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type InputGroupButtonVariants = VariantProps<typeof inputGroupButtonVariants>
|
||||||
|
|
||||||
|
export interface InputGroupButtonProps {
|
||||||
|
variant?: ButtonVariants["variant"]
|
||||||
|
size?: InputGroupButtonVariants["size"]
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from 'vue'
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from '@/lib/utils'
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useVModel } from '@vueuse/core'
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
defaultValue?: string | number
|
defaultValue?: string | number
|
||||||
modelValue?: string | number
|
modelValue?: string | number
|
||||||
class?: HTMLAttributes['class']
|
class?: HTMLAttributes["class"]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: 'update:modelValue', payload: string | number): void
|
(e: "update:modelValue", payload: string | number): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
const modelValue = useVModel(props, "modelValue", emits, {
|
||||||
passive: true,
|
passive: true,
|
||||||
defaultValue: props.defaultValue,
|
defaultValue: props.defaultValue,
|
||||||
})
|
})
|
||||||
@@ -24,7 +24,7 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
|||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'file:text-foreground placeholder:text-muted-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-input px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||||
props.class,
|
props.class,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { default as Input } from './Input.vue'
|
export { default as Input } from "./Input.vue"
|
||||||
|
|||||||
Reference in New Issue
Block a user