@spavn/ui 0.0.1 → 0.1.0

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.
@@ -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
- const props = defineProps<{ class?: string }>()
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 :class="cn('animate-pulse rounded-md bg-muted', props.class)" />
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>
@@ -8,11 +8,13 @@ import {
8
8
  } from 'radix-vue'
9
9
  import { computed } from 'vue'
10
10
  import { cn } from '@/lib/utils'
11
+ import { type SpavnColor, colorBg, colorBorder } from '@/lib/types'
11
12
 
12
13
  interface Props {
13
14
  class?: string
14
15
  modelValue?: number[]
15
16
  showValue?: boolean
17
+ color?: SpavnColor
16
18
  }
17
19
 
18
20
  const props = withDefaults(defineProps<Props>(), {
@@ -20,6 +22,16 @@ const props = withDefaults(defineProps<Props>(), {
20
22
  })
21
23
 
22
24
  const thumbCount = computed(() => (props.modelValue ?? [0]).length)
25
+
26
+ const rangeColor = computed(() => {
27
+ if (!props.color || props.color === 'default') return 'bg-primary'
28
+ return colorBg[props.color as Exclude<SpavnColor, 'default'>]
29
+ })
30
+
31
+ const thumbBorderColor = computed(() => {
32
+ if (!props.color || props.color === 'default') return 'border-primary'
33
+ return colorBorder[props.color as Exclude<SpavnColor, 'default'>]
34
+ })
23
35
  </script>
24
36
 
25
37
  <template>
@@ -37,14 +49,15 @@ const thumbCount = computed(() => (props.modelValue ?? [0]).length)
37
49
  <SliderTrack
38
50
  class="relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted shadow-depth-1"
39
51
  >
40
- <SliderRange class="absolute h-full bg-primary transition-all duration-150 ease-out" />
52
+ <SliderRange :class="cn('absolute h-full transition-all duration-150 ease-out', rangeColor)" />
41
53
  </SliderTrack>
42
54
  <SliderThumb
43
55
  v-for="(_, i) in thumbCount"
44
56
  :key="i"
45
57
  :class="
46
58
  cn(
47
- 'block h-5 w-5 rounded-full border-2 border-primary bg-background',
59
+ 'block h-5 w-5 rounded-full border-2 bg-background',
60
+ thumbBorderColor,
48
61
  'ring-offset-background transition-all duration-150 ease-out',
49
62
  'shadow-depth-2 hover:shadow-depth-3 hover:scale-105',
50
63
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
@@ -53,7 +66,7 @@ const thumbCount = computed(() => (props.modelValue ?? [0]).length)
53
66
  "
54
67
  />
55
68
  </SliderRoot>
56
-
69
+
57
70
  <!-- Value Display Slot -->
58
71
  <slot name="value" :value="modelValue">
59
72
  <span
package/src/lib/styles.ts CHANGED
@@ -48,6 +48,45 @@ export const baseStyles = `
48
48
  --input: 0 0% 90%;
49
49
  --ring: 0 0% 9%;
50
50
 
51
+ /* Extended palette */
52
+ --info: 217 91% 60%;
53
+ --info-foreground: 0 0% 98%;
54
+ --info-soft: 217 91% 95%;
55
+
56
+ --warning: 38 92% 50%;
57
+ --warning-foreground: 0 0% 9%;
58
+ --warning-soft: 38 92% 95%;
59
+
60
+ --amber: 45 93% 47%;
61
+ --amber-foreground: 0 0% 9%;
62
+ --amber-soft: 45 93% 95%;
63
+
64
+ --blue: 221 83% 53%;
65
+ --blue-foreground: 0 0% 98%;
66
+ --blue-soft: 221 83% 95%;
67
+
68
+ --green: 142 71% 45%;
69
+ --green-foreground: 0 0% 98%;
70
+ --green-soft: 142 71% 95%;
71
+
72
+ --purple: 262 83% 58%;
73
+ --purple-foreground: 0 0% 98%;
74
+ --purple-soft: 262 83% 95%;
75
+
76
+ --pink: 330 81% 60%;
77
+ --pink-foreground: 0 0% 98%;
78
+ --pink-soft: 330 81% 95%;
79
+
80
+ --orange: 25 95% 53%;
81
+ --orange-foreground: 0 0% 98%;
82
+ --orange-soft: 25 95% 95%;
83
+
84
+ /* Soft variants for semantic colors */
85
+ --primary-soft: 0 0% 93%;
86
+ --secondary-soft: 0 0% 96%;
87
+ --destructive-soft: 0 84% 95%;
88
+ --success-soft: 142 76% 95%;
89
+
51
90
  /* Radius - updated to match design system */
52
91
  --radius: 1rem;
53
92
 
@@ -110,6 +149,45 @@ export const baseStyles = `
110
149
  --input: 0 0% 20%;
111
150
  --ring: 0 0% 80%;
112
151
 
152
+ /* Extended palette - dark mode (reduced saturation) */
153
+ --info: 217 80% 55%;
154
+ --info-foreground: 0 0% 98%;
155
+ --info-soft: 217 50% 15%;
156
+
157
+ --warning: 38 80% 50%;
158
+ --warning-foreground: 0 0% 9%;
159
+ --warning-soft: 38 50% 15%;
160
+
161
+ --amber: 45 80% 50%;
162
+ --amber-foreground: 0 0% 9%;
163
+ --amber-soft: 45 50% 15%;
164
+
165
+ --blue: 221 75% 55%;
166
+ --blue-foreground: 0 0% 98%;
167
+ --blue-soft: 221 50% 15%;
168
+
169
+ --green: 142 65% 45%;
170
+ --green-foreground: 0 0% 98%;
171
+ --green-soft: 142 40% 15%;
172
+
173
+ --purple: 262 75% 60%;
174
+ --purple-foreground: 0 0% 98%;
175
+ --purple-soft: 262 50% 15%;
176
+
177
+ --pink: 330 70% 55%;
178
+ --pink-foreground: 0 0% 98%;
179
+ --pink-soft: 330 50% 15%;
180
+
181
+ --orange: 25 85% 55%;
182
+ --orange-foreground: 0 0% 98%;
183
+ --orange-soft: 25 50% 15%;
184
+
185
+ /* Soft variants for semantic colors - dark mode */
186
+ --primary-soft: 0 0% 15%;
187
+ --secondary-soft: 0 0% 12%;
188
+ --destructive-soft: 0 60% 15%;
189
+ --success-soft: 142 50% 15%;
190
+
113
191
  /* Elevation opacity overlays for depth perception (inverted for dark mode) */
114
192
  --elevation-overlay-1: rgba(255, 255, 255, 0.03);
115
193
  --elevation-overlay-2: rgba(255, 255, 255, 0.06);
@@ -4,14 +4,33 @@ import {
4
4
  SwitchRoot,
5
5
  SwitchThumb,
6
6
  } from 'radix-vue'
7
+ import { computed } from 'vue'
7
8
  import { cn } from '@/lib/utils'
9
+ import { type SpavnColor, colorCheckedBg } from '@/lib/types'
8
10
 
9
11
  interface Props {
10
12
  class?: string
11
13
  checked?: boolean
14
+ size?: 'sm' | 'default' | 'lg'
15
+ color?: SpavnColor
12
16
  }
13
17
 
14
- const props = defineProps<Props>()
18
+ const props = withDefaults(defineProps<Props>(), {
19
+ size: 'default',
20
+ })
21
+
22
+ const sizeConfig = {
23
+ sm: { track: 'h-4 w-8', thumb: 'h-3 w-3', translate: 'data-[state=checked]:translate-x-4' },
24
+ default: { track: 'h-6 w-11', thumb: 'h-5 w-5', translate: 'data-[state=checked]:translate-x-5' },
25
+ lg: { track: 'h-7 w-14', thumb: 'h-6 w-6', translate: 'data-[state=checked]:translate-x-7' },
26
+ }
27
+
28
+ const currentSize = computed(() => sizeConfig[props.size || 'default'])
29
+
30
+ const checkedColor = computed(() => {
31
+ if (!props.color || props.color === 'default') return 'data-[state=checked]:bg-primary'
32
+ return colorCheckedBg[props.color as Exclude<SpavnColor, 'default'>]
33
+ })
15
34
  </script>
16
35
 
17
36
  <template>
@@ -20,8 +39,10 @@ const props = defineProps<Props>()
20
39
  :checked="checked"
21
40
  :class="
22
41
  cn(
23
- 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-depth-1',
24
- 'bg-muted data-[state=checked]:bg-primary',
42
+ 'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-depth-1',
43
+ currentSize.track,
44
+ 'bg-muted',
45
+ checkedColor,
25
46
  'ring-offset-background transition-all duration-150 ease-out',
26
47
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
27
48
  'disabled:cursor-not-allowed disabled:opacity-50',
@@ -33,9 +54,11 @@ const props = defineProps<Props>()
33
54
  <SwitchThumb
34
55
  :class="
35
56
  cn(
36
- 'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-depth-2',
57
+ 'pointer-events-none block rounded-full bg-background shadow-depth-2',
58
+ currentSize.thumb,
37
59
  'ring-0 transition-transform duration-150 ease-out',
38
- 'data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0'
60
+ currentSize.translate,
61
+ 'data-[state=unchecked]:translate-x-0'
39
62
  )
40
63
  "
41
64
  />
@@ -1,20 +1,50 @@
1
1
  <script setup lang="ts">
2
2
  defineOptions({ inheritAttrs: false })
3
3
  import { TabsList } from 'radix-vue'
4
+ import { computed, provide } from 'vue'
4
5
  import { cn } from '@/lib/utils'
6
+ import { type SpavnColor } from '@/lib/types'
5
7
 
6
- const props = defineProps<{ class?: string }>()
8
+ interface Props {
9
+ class?: string
10
+ variant?: 'default' | 'underline' | 'pills' | 'enclosed'
11
+ size?: 'sm' | 'default' | 'lg'
12
+ color?: SpavnColor
13
+ }
14
+
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ variant: 'default',
17
+ size: 'default',
18
+ })
19
+
20
+ // Provide variant and color to TabsTrigger children
21
+ provide('tabs-variant', props.variant)
22
+ provide('tabs-color', props.color)
23
+
24
+ const sizeClasses = {
25
+ sm: 'h-8 text-xs',
26
+ default: 'h-10 text-sm',
27
+ lg: 'h-12 text-base',
28
+ }
29
+
30
+ const variantClasses = computed(() => {
31
+ switch (props.variant) {
32
+ case 'underline':
33
+ return 'inline-flex items-center justify-center bg-transparent border-b border-border p-0 gap-0 rounded-none shadow-none'
34
+ case 'pills':
35
+ return 'inline-flex items-center justify-center bg-transparent p-0 gap-1 rounded-none shadow-none'
36
+ case 'enclosed':
37
+ return 'inline-flex items-center justify-center bg-muted p-1 rounded-lg shadow-depth-1 border border-border'
38
+ default:
39
+ return 'inline-flex items-center justify-center rounded-md bg-muted p-1 shadow-depth-1'
40
+ }
41
+ })
7
42
  </script>
8
43
 
9
44
  <template>
10
45
  <TabsList
11
46
  v-bind="$attrs"
12
- :class="
13
- cn(
14
- 'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground shadow-depth-1',
15
- props.class
16
- )
17
- "
47
+ :class="cn(variantClasses, sizeClasses[props.size], 'text-muted-foreground', props.class)"
18
48
  >
19
49
  <slot />
20
50
  </TabsList>
@@ -1,20 +1,50 @@
1
1
  <script setup lang="ts">
2
2
  defineOptions({ inheritAttrs: false })
3
3
  import { TabsTrigger } from 'radix-vue'
4
+ import { inject, computed } from 'vue'
4
5
  import { cn } from '@/lib/utils'
6
+ import { type SpavnColor, colorActiveUnderline, colorActivePill } from '@/lib/types'
5
7
 
6
8
  const props = defineProps<{ class?: string }>()
9
+
10
+ const variant = inject<string>('tabs-variant', 'default')
11
+ const color = inject<SpavnColor | undefined>('tabs-color', undefined)
12
+
13
+ const activeColorClass = computed(() => {
14
+ if (!color || color === 'default') return ''
15
+ const c = color as Exclude<SpavnColor, 'default'>
16
+
17
+ switch (variant) {
18
+ case 'underline':
19
+ return colorActiveUnderline[c]
20
+ case 'pills':
21
+ case 'enclosed':
22
+ return colorActivePill[c]
23
+ default:
24
+ return colorActivePill[c]
25
+ }
26
+ })
27
+
28
+ const variantClasses = computed(() => {
29
+ const base = 'inline-flex items-center justify-center whitespace-nowrap px-3 py-1.5 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'
30
+
31
+ switch (variant) {
32
+ case 'underline':
33
+ return `${base} rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:text-foreground`
34
+ case 'pills':
35
+ return `${base} rounded-full data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-depth-1`
36
+ case 'enclosed':
37
+ return `${base} rounded-md data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm`
38
+ default:
39
+ return `${base} rounded-sm data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm`
40
+ }
41
+ })
7
42
  </script>
8
43
 
9
44
  <template>
10
45
  <TabsTrigger
11
46
  v-bind="$attrs"
12
- :class="
13
- cn(
14
- 'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 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-sm',
15
- props.class
16
- )
17
- "
47
+ :class="cn(variantClasses, activeColorClass, props.class)"
18
48
  >
19
49
  <slot />
20
50
  </TabsTrigger>
@@ -1,30 +1,56 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from 'vue'
2
3
  import { cn } from '@/lib/utils'
4
+ import { type SpavnRadius, radiusMap } from '@/lib/types'
3
5
 
4
6
  interface Props {
5
7
  class?: string
6
8
  disabled?: boolean
7
9
  placeholder?: string
8
10
  modelValue?: string
11
+ variant?: 'outline' | 'filled' | 'underline' | 'plain'
12
+ size?: 'sm' | 'default' | 'lg'
13
+ radius?: SpavnRadius
9
14
  }
10
15
 
11
- const props = defineProps<Props>()
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ variant: 'outline',
18
+ size: 'default',
19
+ })
12
20
 
13
21
  defineEmits<{
14
22
  'update:modelValue': [value: string]
15
23
  }>()
24
+
25
+ const sizeClasses = {
26
+ sm: 'min-h-[60px] text-sm',
27
+ default: 'min-h-[80px] text-sm',
28
+ lg: 'min-h-[120px] text-base',
29
+ }
30
+
31
+ const variantClasses = {
32
+ outline: 'border border-input bg-background shadow-depth-1 focus-visible:shadow-depth-2',
33
+ filled: 'border-transparent bg-muted',
34
+ underline: 'border-b border-input bg-transparent rounded-none',
35
+ plain: 'border-transparent bg-transparent focus-visible:shadow-none',
36
+ }
37
+
38
+ const radiusClass = computed(() => props.radius ? radiusMap[props.radius] : '')
16
39
  </script>
17
40
 
18
41
  <template>
19
42
  <textarea
20
43
  :class="
21
44
  cn(
22
- 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
45
+ 'flex w-full rounded-md px-3 py-2',
46
+ variantClasses[props.variant],
47
+ sizeClasses[props.size],
23
48
  'ring-offset-background',
24
49
  'placeholder:text-muted-foreground',
25
50
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
26
51
  'disabled:cursor-not-allowed disabled:opacity-50',
27
- 'shadow-depth-1 focus-visible:shadow-depth-2 transition-elevation',
52
+ 'transition-elevation',
53
+ radiusClass,
28
54
  props.class
29
55
  )
30
56
  "
@@ -1,20 +1,28 @@
1
1
  <script setup lang="ts">
2
2
  defineOptions({ inheritAttrs: false })
3
3
  import { Toggle } from 'radix-vue'
4
+ import { computed } from 'vue'
4
5
  import { cn } from '@/lib/utils'
6
+ import { type SpavnColor, colorOnState } from '@/lib/types'
5
7
  import { toggleVariants } from './variants'
6
8
 
7
9
  const props = defineProps<{
8
10
  class?: string
9
11
  variant?: 'default' | 'outline'
10
12
  size?: 'default' | 'sm' | 'lg'
13
+ color?: SpavnColor
11
14
  }>()
15
+
16
+ const activeColorClass = computed(() => {
17
+ if (!props.color || props.color === 'default') return ''
18
+ return colorOnState[props.color as Exclude<SpavnColor, 'default'>]
19
+ })
12
20
  </script>
13
21
 
14
22
  <template>
15
23
  <Toggle
16
24
  v-bind="$attrs"
17
- :class="cn(toggleVariants({ variant: props.variant, size: props.size }), props.class)"
25
+ :class="cn(toggleVariants({ variant: props.variant, size: props.size }), activeColorClass, props.class)"
18
26
  >
19
27
  <slot />
20
28
  </Toggle>