@spavn/ui 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/registry.d.ts.map +1 -1
- package/cli/registry.js +43 -16
- package/cli/registry.js.map +1 -1
- package/dist/index.js +3001 -2308
- package/dist/index.umd.cjs +80 -2
- package/mcp-server/index.js +74 -39
- package/mcp-server/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/lib/alert/Alert.vue +17 -1
- package/src/lib/alert/variants.ts +3 -0
- package/src/lib/avatar/Avatar.vue +27 -3
- package/src/lib/avatar/AvatarFallback.vue +14 -1
- package/src/lib/avatar-group/AvatarGroup.vue +27 -0
- package/src/lib/avatar-group/index.ts +1 -0
- package/src/lib/badge/Badge.vue +30 -1
- package/src/lib/badge/variants.ts +3 -0
- package/src/lib/button/Button.vue +79 -24
- package/src/lib/button/variants.ts +15 -0
- package/src/lib/card/Card.vue +15 -2
- package/src/lib/checkbox/Checkbox.vue +32 -3
- package/src/lib/input/Input.vue +91 -39
- package/src/lib/progress/Progress.vue +13 -3
- package/src/lib/radio-group/RadioGroupItem.vue +32 -3
- package/src/lib/separator/Separator.vue +37 -8
- package/src/lib/skeleton/Skeleton.vue +27 -2
- package/src/lib/slider/Slider.vue +16 -3
- package/src/lib/styles.ts +78 -0
- package/src/lib/switch/Switch.vue +28 -5
- package/src/lib/tabs/TabsList.vue +37 -7
- package/src/lib/tabs/TabsTrigger.vue +36 -6
- package/src/lib/textarea/Textarea.vue +29 -3
- package/src/lib/toggle/Toggle.vue +9 -1
- package/src/lib/types.ts +370 -0
- package/src/lib/utils.ts +23 -2
- package/src/theme.css +39 -0
|
@@ -9,6 +9,9 @@ export const badgeVariants = cva(
|
|
|
9
9
|
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
|
10
10
|
destructive: 'border-transparent bg-destructive text-destructive-foreground shadow-depth-1',
|
|
11
11
|
outline: 'text-foreground',
|
|
12
|
+
soft: 'border-transparent',
|
|
13
|
+
surface: '',
|
|
14
|
+
dot: 'border-transparent bg-transparent text-foreground font-normal gap-1.5',
|
|
12
15
|
},
|
|
13
16
|
size: {
|
|
14
17
|
sm: 'px-2 py-0 text-[10px]',
|
|
@@ -1,61 +1,116 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
2
3
|
import { cn } from '@/lib/utils'
|
|
4
|
+
import { type SpavnColor, type SpavnRadius, colorClasses, radiusMap, colorHoverSolid, colorHoverOutline, colorHoverGhost, colorHoverLink } from '@/lib/types'
|
|
3
5
|
import { buttonVariants, type ButtonVariants } from './variants'
|
|
4
6
|
|
|
5
7
|
interface Props {
|
|
6
8
|
variant?: ButtonVariants['variant']
|
|
7
9
|
size?: ButtonVariants['size']
|
|
8
10
|
elevation?: ButtonVariants['elevation']
|
|
11
|
+
color?: SpavnColor
|
|
12
|
+
radius?: SpavnRadius
|
|
9
13
|
class?: string
|
|
10
14
|
disabled?: boolean
|
|
11
15
|
type?: 'button' | 'submit' | 'reset'
|
|
12
16
|
loading?: boolean
|
|
17
|
+
loadingPosition?: 'left' | 'right' | 'center'
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
const props = withDefaults(defineProps<Props>(), {
|
|
16
21
|
type: 'button',
|
|
17
22
|
loading: false,
|
|
23
|
+
loadingPosition: 'center',
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const colorStyle = computed(() => {
|
|
27
|
+
if (!props.color || props.color === 'default') return ''
|
|
28
|
+
const c = props.color as Exclude<SpavnColor, 'default'>
|
|
29
|
+
const v = props.variant || 'default'
|
|
30
|
+
|
|
31
|
+
// Base color classes from shared utility
|
|
32
|
+
const styleMap: Record<string, 'solid' | 'soft' | 'surface' | 'outline'> = {
|
|
33
|
+
default: 'solid',
|
|
34
|
+
destructive: 'solid',
|
|
35
|
+
soft: 'soft',
|
|
36
|
+
surface: 'surface',
|
|
37
|
+
outline: 'outline',
|
|
38
|
+
secondary: 'solid',
|
|
39
|
+
ghost: 'soft',
|
|
40
|
+
link: 'outline',
|
|
41
|
+
plain: 'soft',
|
|
42
|
+
}
|
|
43
|
+
const base = colorClasses(props.color, styleMap[v] || 'solid')
|
|
44
|
+
|
|
45
|
+
// Static hover/active class maps (no template literals)
|
|
46
|
+
const hoverMap: Record<string, string> = {
|
|
47
|
+
default: colorHoverSolid[c],
|
|
48
|
+
destructive: colorHoverSolid[c],
|
|
49
|
+
secondary: colorHoverSolid[c],
|
|
50
|
+
outline: colorHoverOutline[c],
|
|
51
|
+
ghost: colorHoverGhost[c],
|
|
52
|
+
soft: '',
|
|
53
|
+
surface: '',
|
|
54
|
+
link: colorHoverLink[c],
|
|
55
|
+
plain: colorHoverGhost[c],
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return `${base} ${hoverMap[v] || ''}`
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const radiusClass = computed(() => props.radius ? radiusMap[props.radius] : '')
|
|
62
|
+
|
|
63
|
+
const iconSizeClass = computed(() => {
|
|
64
|
+
const map: Record<string, string> = {
|
|
65
|
+
sm: 'h-3.5 w-3.5',
|
|
66
|
+
default: 'h-4 w-4',
|
|
67
|
+
lg: 'h-5 w-5',
|
|
68
|
+
icon: 'h-4 w-4',
|
|
69
|
+
}
|
|
70
|
+
return map[props.size || 'default'] || 'h-4 w-4'
|
|
18
71
|
})
|
|
19
72
|
</script>
|
|
20
73
|
|
|
21
74
|
<template>
|
|
22
75
|
<button
|
|
23
76
|
:type="type"
|
|
24
|
-
:class="cn(buttonVariants({ variant, size, elevation }), props.class)"
|
|
77
|
+
:class="cn(buttonVariants({ variant, size, elevation }), colorStyle, radiusClass, props.class)"
|
|
25
78
|
:disabled="disabled || loading"
|
|
26
79
|
>
|
|
27
|
-
<!--
|
|
80
|
+
<!-- Center loading (current behavior) -->
|
|
28
81
|
<span
|
|
29
|
-
v-if="loading"
|
|
82
|
+
v-if="loading && loadingPosition === 'center'"
|
|
30
83
|
class="absolute inset-0 flex items-center justify-center"
|
|
31
84
|
>
|
|
32
|
-
<svg
|
|
33
|
-
class="
|
|
34
|
-
|
|
35
|
-
fill="none"
|
|
36
|
-
viewBox="0 0 24 24"
|
|
37
|
-
>
|
|
38
|
-
<circle
|
|
39
|
-
class="opacity-25"
|
|
40
|
-
cx="12"
|
|
41
|
-
cy="12"
|
|
42
|
-
r="10"
|
|
43
|
-
stroke="currentColor"
|
|
44
|
-
stroke-width="4"
|
|
45
|
-
/>
|
|
46
|
-
<path
|
|
47
|
-
class="opacity-75"
|
|
48
|
-
fill="currentColor"
|
|
49
|
-
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
50
|
-
/>
|
|
85
|
+
<svg class="animate-spin" :class="iconSizeClass" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
86
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
87
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
51
88
|
</svg>
|
|
52
89
|
</span>
|
|
53
|
-
|
|
90
|
+
|
|
54
91
|
<span
|
|
55
|
-
:class="{ 'opacity-0': loading }"
|
|
92
|
+
:class="{ 'opacity-0': loading && loadingPosition === 'center' }"
|
|
56
93
|
class="inline-flex items-center justify-center gap-2"
|
|
57
94
|
>
|
|
95
|
+
<!-- Left: loading spinner or left-icon slot -->
|
|
96
|
+
<svg v-if="loading && loadingPosition === 'left'" class="animate-spin" :class="iconSizeClass" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
97
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
98
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
99
|
+
</svg>
|
|
100
|
+
<span v-else-if="$slots['left-icon']" :class="iconSizeClass" class="inline-flex items-center justify-center shrink-0">
|
|
101
|
+
<slot name="left-icon" />
|
|
102
|
+
</span>
|
|
103
|
+
|
|
58
104
|
<slot />
|
|
105
|
+
|
|
106
|
+
<!-- Right: loading spinner or right-icon slot -->
|
|
107
|
+
<svg v-if="loading && loadingPosition === 'right'" class="animate-spin" :class="iconSizeClass" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
108
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
109
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
110
|
+
</svg>
|
|
111
|
+
<span v-else-if="$slots['right-icon']" :class="iconSizeClass" class="inline-flex items-center justify-center shrink-0">
|
|
112
|
+
<slot name="right-icon" />
|
|
113
|
+
</span>
|
|
59
114
|
</span>
|
|
60
115
|
</button>
|
|
61
116
|
</template>
|
|
@@ -42,6 +42,21 @@ export const buttonVariants = cva(
|
|
|
42
42
|
'text-primary underline-offset-4 hover:underline',
|
|
43
43
|
'hover:translate-y-0',
|
|
44
44
|
].join(' '),
|
|
45
|
+
soft: [
|
|
46
|
+
'border-transparent',
|
|
47
|
+
'hover:opacity-80 hover:shadow-depth-2',
|
|
48
|
+
'active:opacity-90',
|
|
49
|
+
].join(' '),
|
|
50
|
+
surface: [
|
|
51
|
+
'hover:opacity-80 hover:shadow-depth-2',
|
|
52
|
+
'active:opacity-90',
|
|
53
|
+
].join(' '),
|
|
54
|
+
plain: [
|
|
55
|
+
'border-transparent bg-transparent',
|
|
56
|
+
'hover:bg-accent hover:text-accent-foreground',
|
|
57
|
+
'active:bg-accent/80',
|
|
58
|
+
'hover:translate-y-0',
|
|
59
|
+
].join(' '),
|
|
45
60
|
},
|
|
46
61
|
size: {
|
|
47
62
|
default: 'h-10 px-4 py-2.5',
|
package/src/lib/card/Card.vue
CHANGED
|
@@ -5,11 +5,15 @@ interface Props {
|
|
|
5
5
|
class?: string
|
|
6
6
|
elevation?: 0 | 1 | 2 | 3
|
|
7
7
|
interactive?: boolean
|
|
8
|
+
direction?: 'vertical' | 'horizontal'
|
|
9
|
+
variant?: 'elevated' | 'outlined' | 'filled' | 'ghost'
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
const props = withDefaults(defineProps<Props>(), {
|
|
11
13
|
elevation: 1,
|
|
12
14
|
interactive: false,
|
|
15
|
+
direction: 'vertical',
|
|
16
|
+
variant: 'elevated',
|
|
13
17
|
})
|
|
14
18
|
|
|
15
19
|
const elevationClasses = {
|
|
@@ -18,14 +22,23 @@ const elevationClasses = {
|
|
|
18
22
|
2: 'shadow-depth-2',
|
|
19
23
|
3: 'shadow-depth-3',
|
|
20
24
|
}
|
|
25
|
+
|
|
26
|
+
const variantClasses = {
|
|
27
|
+
elevated: 'border bg-card text-card-foreground',
|
|
28
|
+
outlined: 'border bg-transparent text-card-foreground shadow-none',
|
|
29
|
+
filled: 'border-transparent bg-muted text-foreground shadow-none',
|
|
30
|
+
ghost: 'border-transparent bg-transparent text-foreground shadow-none',
|
|
31
|
+
}
|
|
21
32
|
</script>
|
|
22
33
|
|
|
23
34
|
<template>
|
|
24
35
|
<div
|
|
25
36
|
:class="
|
|
26
37
|
cn(
|
|
27
|
-
'rounded-lg
|
|
28
|
-
|
|
38
|
+
'rounded-lg',
|
|
39
|
+
variantClasses[variant],
|
|
40
|
+
variant === 'elevated' && elevationClasses[elevation],
|
|
41
|
+
direction === 'horizontal' && 'flex flex-row',
|
|
29
42
|
interactive && 'transition-elevation hover:shadow-depth-2 cursor-pointer',
|
|
30
43
|
props.class
|
|
31
44
|
)
|
|
@@ -4,9 +4,36 @@ import {
|
|
|
4
4
|
CheckboxIndicator,
|
|
5
5
|
CheckboxRoot,
|
|
6
6
|
} from 'radix-vue'
|
|
7
|
+
import { computed } from 'vue'
|
|
7
8
|
import { cn } from '@/lib/utils'
|
|
9
|
+
import { type SpavnColor, colorCheckedFull } from '@/lib/types'
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
interface Props {
|
|
12
|
+
class?: string
|
|
13
|
+
size?: 'sm' | 'default' | 'lg'
|
|
14
|
+
color?: SpavnColor
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
size: 'default',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const sizeClasses = {
|
|
22
|
+
sm: 'h-3.5 w-3.5',
|
|
23
|
+
default: 'h-4 w-4',
|
|
24
|
+
lg: 'h-5 w-5',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const iconSizeClasses = {
|
|
28
|
+
sm: 'h-3 w-3',
|
|
29
|
+
default: 'h-4 w-4',
|
|
30
|
+
lg: 'h-5 w-5',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const colorCls = computed(() => {
|
|
34
|
+
const c = props.color && props.color !== 'default' ? props.color as Exclude<SpavnColor, 'default'> : 'primary'
|
|
35
|
+
return colorCheckedFull[c]
|
|
36
|
+
})
|
|
10
37
|
</script>
|
|
11
38
|
|
|
12
39
|
<template>
|
|
@@ -14,7 +41,9 @@ const props = defineProps<{ class?: string }>()
|
|
|
14
41
|
v-bind="$attrs"
|
|
15
42
|
:class="
|
|
16
43
|
cn(
|
|
17
|
-
'peer
|
|
44
|
+
'peer shrink-0 rounded-sm border shadow-depth-1 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
45
|
+
sizeClasses[props.size],
|
|
46
|
+
colorCls,
|
|
18
47
|
props.class
|
|
19
48
|
)
|
|
20
49
|
"
|
|
@@ -30,7 +59,7 @@ const props = defineProps<{ class?: string }>()
|
|
|
30
59
|
stroke-width="2"
|
|
31
60
|
stroke-linecap="round"
|
|
32
61
|
stroke-linejoin="round"
|
|
33
|
-
class="
|
|
62
|
+
:class="iconSizeClasses[props.size]"
|
|
34
63
|
>
|
|
35
64
|
<path d="M20 6 9 17l-5-5" />
|
|
36
65
|
</svg>
|
package/src/lib/input/Input.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, useSlots } from 'vue'
|
|
3
3
|
import { cn } from '@/lib/utils'
|
|
4
|
+
import { type SpavnRadius, radiusMap } from '@/lib/types'
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
class?: string
|
|
@@ -8,20 +9,34 @@ interface Props {
|
|
|
8
9
|
disabled?: boolean
|
|
9
10
|
placeholder?: string
|
|
10
11
|
modelValue?: string | number
|
|
11
|
-
size?: 'sm' | 'default' | 'lg'
|
|
12
|
+
size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl'
|
|
13
|
+
variant?: 'outline' | 'filled' | 'underline' | 'plain'
|
|
14
|
+
radius?: SpavnRadius
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
const props = withDefaults(defineProps<Props>(), {
|
|
15
18
|
type: 'text',
|
|
16
19
|
size: 'default',
|
|
20
|
+
variant: 'outline',
|
|
17
21
|
})
|
|
18
22
|
|
|
19
|
-
const sizeClasses = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const sizeClasses: Record<string, string> = {
|
|
24
|
+
xs: 'h-7 text-xs px-2',
|
|
25
|
+
sm: 'h-8 text-xs px-3',
|
|
26
|
+
default: 'h-10 text-sm px-3',
|
|
27
|
+
lg: 'h-12 text-base px-4',
|
|
28
|
+
xl: 'h-14 text-base px-4',
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
const variantClasses = {
|
|
32
|
+
outline: 'border border-input bg-background shadow-depth-1 focus-visible:shadow-depth-2 rounded-lg',
|
|
33
|
+
filled: 'border-transparent bg-muted rounded-lg',
|
|
34
|
+
underline: 'border-b border-input bg-transparent rounded-none px-0 focus-visible:shadow-none',
|
|
35
|
+
plain: 'border-transparent bg-transparent rounded-lg focus-visible:shadow-none',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const radiusClass = computed(() => props.radius ? radiusMap[props.radius] : '')
|
|
39
|
+
|
|
25
40
|
const emit = defineEmits<{
|
|
26
41
|
(e: 'update:modelValue', value: string): void
|
|
27
42
|
}>()
|
|
@@ -30,6 +45,12 @@ const slots = useSlots()
|
|
|
30
45
|
|
|
31
46
|
const hasLeftIcon = computed(() => !!slots['left-icon'])
|
|
32
47
|
const hasRightIcon = computed(() => !!slots['right-icon'])
|
|
48
|
+
const hasPrefix = computed(() => !!slots.prefix)
|
|
49
|
+
const hasSuffix = computed(() => !!slots.suffix)
|
|
50
|
+
|
|
51
|
+
const addonHeightClass = computed(() => {
|
|
52
|
+
return sizeClasses[props.size].split(' ').find((c: string) => c.startsWith('h-')) || 'h-10'
|
|
53
|
+
})
|
|
33
54
|
|
|
34
55
|
const handleInput = (event: Event) => {
|
|
35
56
|
const target = event.target as HTMLInputElement
|
|
@@ -39,47 +60,78 @@ const handleInput = (event: Event) => {
|
|
|
39
60
|
|
|
40
61
|
<template>
|
|
41
62
|
<div class="relative flex items-center w-full">
|
|
42
|
-
<!--
|
|
63
|
+
<!-- Prefix addon -->
|
|
43
64
|
<div
|
|
44
|
-
v-if="
|
|
45
|
-
class="
|
|
65
|
+
v-if="hasPrefix"
|
|
66
|
+
:class="cn(
|
|
67
|
+
'inline-flex items-center px-3 text-sm text-muted-foreground bg-muted border border-input border-r-0',
|
|
68
|
+
props.variant === 'outline' ? 'rounded-l-lg' : '',
|
|
69
|
+
addonHeightClass
|
|
70
|
+
)"
|
|
46
71
|
>
|
|
47
|
-
<slot name="
|
|
72
|
+
<slot name="prefix" />
|
|
48
73
|
</div>
|
|
49
74
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
<!-- Input wrapper -->
|
|
76
|
+
<div class="relative flex items-center w-full">
|
|
77
|
+
<!-- Left Icon -->
|
|
78
|
+
<div
|
|
79
|
+
v-if="hasLeftIcon"
|
|
80
|
+
class="absolute left-3 flex items-center justify-center text-muted-foreground pointer-events-none"
|
|
81
|
+
>
|
|
82
|
+
<slot name="left-icon" />
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<input
|
|
86
|
+
:type="type"
|
|
87
|
+
:value="modelValue"
|
|
88
|
+
:placeholder="placeholder"
|
|
89
|
+
:class="
|
|
90
|
+
cn(
|
|
91
|
+
'flex w-full',
|
|
92
|
+
variantClasses[props.variant],
|
|
93
|
+
sizeClasses[props.size],
|
|
94
|
+
'ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium',
|
|
95
|
+
'placeholder:text-muted-foreground',
|
|
96
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:border-primary',
|
|
97
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
98
|
+
'transition-all duration-150 ease-out',
|
|
99
|
+
// Padding adjustments for icons
|
|
100
|
+
hasLeftIcon && 'pl-10',
|
|
101
|
+
hasRightIcon && 'pr-10',
|
|
102
|
+
!hasLeftIcon && props.variant !== 'underline' && '',
|
|
103
|
+
!hasRightIcon && !hasLeftIcon && 'py-2',
|
|
104
|
+
// Addon border radius adjustments
|
|
105
|
+
hasPrefix && 'rounded-l-none border-l-0',
|
|
106
|
+
hasSuffix && 'rounded-r-none border-r-0',
|
|
107
|
+
radiusClass,
|
|
108
|
+
props.class
|
|
109
|
+
)
|
|
110
|
+
"
|
|
111
|
+
:disabled="disabled"
|
|
112
|
+
@input="handleInput"
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<!-- Right Icon -->
|
|
116
|
+
<div
|
|
117
|
+
v-if="hasRightIcon"
|
|
118
|
+
class="absolute right-3 flex items-center justify-center text-muted-foreground"
|
|
119
|
+
:class="{ 'pointer-events-none': !$slots['right-icon-clickable'] }"
|
|
120
|
+
>
|
|
121
|
+
<slot name="right-icon" />
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
75
124
|
|
|
76
|
-
<!--
|
|
125
|
+
<!-- Suffix addon -->
|
|
77
126
|
<div
|
|
78
|
-
v-if="
|
|
79
|
-
class="
|
|
80
|
-
|
|
127
|
+
v-if="hasSuffix"
|
|
128
|
+
:class="cn(
|
|
129
|
+
'inline-flex items-center px-3 text-sm text-muted-foreground bg-muted border border-input border-l-0',
|
|
130
|
+
props.variant === 'outline' ? 'rounded-r-lg' : '',
|
|
131
|
+
addonHeightClass
|
|
132
|
+
)"
|
|
81
133
|
>
|
|
82
|
-
<slot name="
|
|
134
|
+
<slot name="suffix" />
|
|
83
135
|
</div>
|
|
84
136
|
</div>
|
|
85
137
|
</template>
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
defineOptions({ inheritAttrs: false })
|
|
3
|
+
import { computed } from 'vue'
|
|
3
4
|
import {
|
|
4
5
|
ProgressIndicator,
|
|
5
6
|
ProgressRoot,
|
|
6
7
|
} from 'radix-vue'
|
|
7
8
|
import { cn } from '@/lib/utils'
|
|
9
|
+
import { type SpavnColor, colorBg } from '@/lib/types'
|
|
8
10
|
|
|
9
11
|
const props = defineProps<{
|
|
10
12
|
class?: string
|
|
11
13
|
modelValue?: number
|
|
12
|
-
size?: '
|
|
14
|
+
size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl'
|
|
15
|
+
color?: SpavnColor
|
|
13
16
|
ariaLabel?: string
|
|
14
17
|
}>()
|
|
15
18
|
|
|
16
|
-
const sizeClasses = {
|
|
19
|
+
const sizeClasses: Record<string, string> = {
|
|
20
|
+
xs: 'h-1',
|
|
17
21
|
sm: 'h-1',
|
|
18
22
|
default: 'h-1.5',
|
|
19
23
|
lg: 'h-2',
|
|
24
|
+
xl: 'h-3',
|
|
20
25
|
}
|
|
26
|
+
|
|
27
|
+
const indicatorColor = computed(() => {
|
|
28
|
+
if (!props.color || props.color === 'default') return 'bg-primary'
|
|
29
|
+
return colorBg[props.color as Exclude<SpavnColor, 'default'>]
|
|
30
|
+
})
|
|
21
31
|
</script>
|
|
22
32
|
|
|
23
33
|
<template>
|
|
@@ -34,7 +44,7 @@ const sizeClasses = {
|
|
|
34
44
|
"
|
|
35
45
|
>
|
|
36
46
|
<ProgressIndicator
|
|
37
|
-
:class="cn('h-full w-full flex-1
|
|
47
|
+
:class="cn('h-full w-full flex-1 rounded-full transition-all duration-300 ease-out', indicatorColor)"
|
|
38
48
|
:style="{ transform: `translateX(-${100 - (props.modelValue || 0)}%)` }"
|
|
39
49
|
/>
|
|
40
50
|
</ProgressRoot>
|
|
@@ -4,9 +4,36 @@ import {
|
|
|
4
4
|
RadioGroupIndicator,
|
|
5
5
|
RadioGroupItem,
|
|
6
6
|
} from 'radix-vue'
|
|
7
|
+
import { computed } from 'vue'
|
|
7
8
|
import { cn } from '@/lib/utils'
|
|
9
|
+
import { type SpavnColor, colorBorderText } from '@/lib/types'
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
interface Props {
|
|
12
|
+
class?: string
|
|
13
|
+
size?: 'sm' | 'default' | 'lg'
|
|
14
|
+
color?: SpavnColor
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
size: 'default',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const sizeClasses = {
|
|
22
|
+
sm: 'h-3.5 w-3.5',
|
|
23
|
+
default: 'h-4 w-4',
|
|
24
|
+
lg: 'h-5 w-5',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dotSizeClasses = {
|
|
28
|
+
sm: 'h-2 w-2',
|
|
29
|
+
default: 'h-2.5 w-2.5',
|
|
30
|
+
lg: 'h-3 w-3',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const colorCls = computed(() => {
|
|
34
|
+
const c = props.color && props.color !== 'default' ? props.color as Exclude<SpavnColor, 'default'> : 'primary'
|
|
35
|
+
return colorBorderText[c]
|
|
36
|
+
})
|
|
10
37
|
</script>
|
|
11
38
|
|
|
12
39
|
<template>
|
|
@@ -14,7 +41,9 @@ const props = defineProps<{ class?: string }>()
|
|
|
14
41
|
v-bind="$attrs"
|
|
15
42
|
:class="
|
|
16
43
|
cn(
|
|
17
|
-
'aspect-square
|
|
44
|
+
'aspect-square rounded-full border ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 shadow-depth-1',
|
|
45
|
+
sizeClasses[props.size],
|
|
46
|
+
colorCls,
|
|
18
47
|
props.class
|
|
19
48
|
)
|
|
20
49
|
"
|
|
@@ -26,7 +55,7 @@ const props = defineProps<{ class?: string }>()
|
|
|
26
55
|
height="24"
|
|
27
56
|
viewBox="0 0 24 24"
|
|
28
57
|
fill="currentColor"
|
|
29
|
-
class="
|
|
58
|
+
:class="dotSizeClasses[props.size]"
|
|
30
59
|
>
|
|
31
60
|
<circle cx="12" cy="12" r="6" />
|
|
32
61
|
</svg>
|
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
defineOptions({ inheritAttrs: false })
|
|
3
|
+
import { computed } from 'vue'
|
|
3
4
|
import { Separator } from 'radix-vue'
|
|
4
5
|
import { cn } from '@/lib/utils'
|
|
6
|
+
import { type SpavnColor, colorBg, colorBorder } from '@/lib/types'
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
interface Props {
|
|
9
|
+
class?: string
|
|
10
|
+
orientation?: 'horizontal' | 'vertical'
|
|
11
|
+
variant?: 'solid' | 'dashed' | 'dotted'
|
|
12
|
+
color?: SpavnColor
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
7
16
|
orientation: 'horizontal',
|
|
17
|
+
variant: 'solid',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const separatorClasses = computed(() => {
|
|
21
|
+
const isHorizontal = props.orientation === 'horizontal'
|
|
22
|
+
const hasColor = props.color && props.color !== 'default'
|
|
23
|
+
const c = hasColor ? props.color as Exclude<SpavnColor, 'default'> : null
|
|
24
|
+
const colorBgClass = c ? colorBg[c] : 'bg-border'
|
|
25
|
+
const colorBorderClass = c ? colorBorder[c] : 'border-border'
|
|
26
|
+
|
|
27
|
+
if (props.variant === 'solid') {
|
|
28
|
+
return isHorizontal
|
|
29
|
+
? `h-px w-full ${colorBgClass}`
|
|
30
|
+
: `h-full w-px ${colorBgClass}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (props.variant === 'dashed') {
|
|
34
|
+
return isHorizontal
|
|
35
|
+
? `w-full border-b border-dashed ${colorBorderClass}`
|
|
36
|
+
: `h-full border-l border-dashed ${colorBorderClass}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// dotted
|
|
40
|
+
return isHorizontal
|
|
41
|
+
? `w-full border-b border-dotted ${colorBorderClass}`
|
|
42
|
+
: `h-full border-l border-dotted ${colorBorderClass}`
|
|
8
43
|
})
|
|
9
44
|
</script>
|
|
10
45
|
|
|
@@ -12,12 +47,6 @@ const props = withDefaults(defineProps<{ class?: string; orientation?: 'horizont
|
|
|
12
47
|
<Separator
|
|
13
48
|
v-bind="$attrs"
|
|
14
49
|
:orientation="props.orientation"
|
|
15
|
-
:class="
|
|
16
|
-
cn(
|
|
17
|
-
'shrink-0 bg-border',
|
|
18
|
-
props.orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
|
|
19
|
-
props.class
|
|
20
|
-
)
|
|
21
|
-
"
|
|
50
|
+
:class="cn('shrink-0', separatorClasses, props.class)"
|
|
22
51
|
/>
|
|
23
52
|
</template>
|
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { cn } from '@/lib/utils'
|
|
3
|
+
import { type SpavnRadius, radiusMap } from '@/lib/types'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
interface Props {
|
|
6
|
+
class?: string
|
|
7
|
+
variant?: 'pulse' | 'wave' | 'none'
|
|
8
|
+
radius?: SpavnRadius
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
12
|
+
variant: 'pulse',
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const animationClasses = {
|
|
16
|
+
pulse: 'animate-pulse',
|
|
17
|
+
wave: 'animate-pulse',
|
|
18
|
+
none: '',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const radiusClass = props.radius ? radiusMap[props.radius] : 'rounded-md'
|
|
5
22
|
</script>
|
|
6
23
|
|
|
7
24
|
<template>
|
|
8
|
-
<div
|
|
25
|
+
<div
|
|
26
|
+
:class="cn(
|
|
27
|
+
'bg-muted',
|
|
28
|
+
animationClasses[variant],
|
|
29
|
+
variant === 'wave' && 'bg-gradient-to-r from-muted via-muted/50 to-muted bg-[length:200%_100%]',
|
|
30
|
+
radiusClass,
|
|
31
|
+
props.class
|
|
32
|
+
)"
|
|
33
|
+
/>
|
|
9
34
|
</template>
|