Skip to content

Commit 0d55239

Browse files
authored
Merge pull request #22 from banua-coder/feature/frontend-design-improvements
feat: implement comprehensive frontend design improvements
2 parents d057fd0 + 7096d2d commit 0d55239

File tree

14 files changed

+1972
-59
lines changed

14 files changed

+1972
-59
lines changed

src/App.vue

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
<template>
2-
<div id="app">
3-
<router-view />
2+
<div id="app" class="transition-colors duration-200">
3+
<PageTransition>
4+
<router-view />
5+
</PageTransition>
46
</div>
57
</template>
68

79
<script setup lang="ts">
8-
// App root component
10+
import { onMounted } from 'vue'
11+
import PageTransition from '@/components/PageTransition.vue'
12+
import { useTheme } from '@/composables/useTheme'
13+
14+
// Initialize theme on app start
15+
const { initTheme } = useTheme()
16+
17+
onMounted(() => {
18+
initTheme()
19+
})
920
</script>
1021

1122
<style>

src/components/BaseBadge.vue

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<template>
2+
<span :class="badgeClasses" v-bind="$attrs">
3+
<span v-if="$slots.icon || dot" class="flex items-center mr-1" :class="{ 'mr-0': !$slots.default }" aria-hidden="true">
4+
<slot name="icon">
5+
<div v-if="dot" :class="dotClasses"></div>
6+
</slot>
7+
</span>
8+
9+
<span v-if="$slots.default">
10+
<slot />
11+
</span>
12+
13+
<span v-if="$slots.iconRight" class="flex items-center ml-1" aria-hidden="true">
14+
<slot name="iconRight" />
15+
</span>
16+
</span>
17+
</template>
18+
19+
<script setup lang="ts">
20+
import { computed } from 'vue'
21+
22+
interface Props {
23+
variant?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'
24+
size?: 'xs' | 'sm' | 'md' | 'lg'
25+
dot?: boolean
26+
rounded?: boolean
27+
outline?: boolean
28+
pulse?: boolean
29+
}
30+
31+
const props = withDefaults(defineProps<Props>(), {
32+
variant: 'default',
33+
size: 'md',
34+
dot: false,
35+
rounded: false,
36+
outline: false,
37+
pulse: false
38+
})
39+
40+
// Base classes
41+
const baseClasses = 'inline-flex items-center font-medium transition-all duration-200'
42+
43+
// Size classes
44+
const sizeClasses = {
45+
xs: 'px-2 py-0.5 text-xs',
46+
sm: 'px-2.5 py-1 text-xs',
47+
md: 'px-3 py-1 text-sm',
48+
lg: 'px-4 py-1.5 text-sm'
49+
}
50+
51+
// Variant classes (filled)
52+
const variantClasses = {
53+
default: 'bg-gray-100 text-gray-700',
54+
primary: 'bg-blue-100 text-blue-700',
55+
secondary: 'bg-gray-100 text-gray-700',
56+
success: 'bg-green-100 text-green-700',
57+
warning: 'bg-yellow-100 text-yellow-700',
58+
danger: 'bg-red-100 text-red-700',
59+
info: 'bg-cyan-100 text-cyan-700'
60+
}
61+
62+
// Outline variant classes
63+
const outlineVariantClasses = {
64+
default: 'border border-gray-300 text-gray-700 bg-white',
65+
primary: 'border border-blue-300 text-blue-700 bg-white',
66+
secondary: 'border border-gray-300 text-gray-700 bg-white',
67+
success: 'border border-green-300 text-green-700 bg-white',
68+
warning: 'border border-yellow-300 text-yellow-700 bg-white',
69+
danger: 'border border-red-300 text-red-700 bg-white',
70+
info: 'border border-cyan-300 text-cyan-700 bg-white'
71+
}
72+
73+
// Dot color classes
74+
const dotColorClasses = {
75+
default: 'bg-gray-400',
76+
primary: 'bg-blue-500',
77+
secondary: 'bg-gray-400',
78+
success: 'bg-green-500',
79+
warning: 'bg-yellow-500',
80+
danger: 'bg-red-500',
81+
info: 'bg-cyan-500'
82+
}
83+
84+
// Computed classes
85+
const badgeClasses = computed(() => [
86+
baseClasses,
87+
sizeClasses[props.size],
88+
props.outline ? outlineVariantClasses[props.variant] : variantClasses[props.variant],
89+
{
90+
'rounded-full': props.rounded || props.size === 'xs',
91+
'rounded-lg': !props.rounded && props.size !== 'xs',
92+
'animate-pulse': props.pulse
93+
}
94+
])
95+
96+
const dotClasses = computed(() => [
97+
'rounded-full',
98+
dotColorClasses[props.variant],
99+
{
100+
'w-1.5 h-1.5': props.size === 'xs',
101+
'w-2 h-2': props.size === 'sm' || props.size === 'md',
102+
'w-2.5 h-2.5': props.size === 'lg',
103+
'animate-pulse': props.pulse
104+
}
105+
])
106+
</script>

src/components/BaseButton.vue

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<template>
2+
<component
3+
:is="tag"
4+
:to="to"
5+
:href="href"
6+
:target="target"
7+
:rel="rel"
8+
:type="type"
9+
:disabled="disabled || loading"
10+
:class="buttonClasses"
11+
:aria-label="ariaLabel"
12+
:aria-describedby="ariaDescribedby"
13+
@click="handleClick"
14+
v-bind="$attrs"
15+
>
16+
<span v-if="loading" class="flex items-center justify-center mr-2" aria-hidden="true">
17+
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
18+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
19+
<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"></path>
20+
</svg>
21+
<span class="sr-only">Loading...</span>
22+
</span>
23+
24+
<span v-if="$slots.icon && !loading" class="flex items-center mr-2" aria-hidden="true">
25+
<slot name="icon" />
26+
</span>
27+
28+
<span :class="{ 'opacity-75': loading }">
29+
<slot />
30+
</span>
31+
32+
<span v-if="$slots.iconRight && !loading" class="flex items-center ml-2" aria-hidden="true">
33+
<slot name="iconRight" />
34+
</span>
35+
</component>
36+
</template>
37+
38+
<script setup lang="ts">
39+
import { computed } from 'vue'
40+
41+
interface Props {
42+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'
43+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
44+
tag?: 'button' | 'a' | 'router-link'
45+
to?: string | object
46+
href?: string
47+
target?: string
48+
rel?: string
49+
type?: 'button' | 'submit' | 'reset'
50+
disabled?: boolean
51+
loading?: boolean
52+
block?: boolean
53+
rounded?: boolean
54+
shadow?: boolean
55+
ariaLabel?: string
56+
ariaDescribedby?: string
57+
}
58+
59+
const props = withDefaults(defineProps<Props>(), {
60+
variant: 'primary',
61+
size: 'md',
62+
tag: 'button',
63+
type: 'button',
64+
disabled: false,
65+
loading: false,
66+
block: false,
67+
rounded: false,
68+
shadow: true
69+
})
70+
71+
const emit = defineEmits<{
72+
click: [event: Event]
73+
}>()
74+
75+
// Base classes
76+
const baseClasses = 'inline-flex items-center justify-center font-semibold transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
77+
78+
// Size classes
79+
const sizeClasses = {
80+
xs: 'px-2.5 py-1.5 text-xs',
81+
sm: 'px-3 py-2 text-sm',
82+
md: 'px-4 py-2.5 text-sm',
83+
lg: 'px-6 py-3 text-base',
84+
xl: 'px-8 py-4 text-lg'
85+
}
86+
87+
// Variant classes
88+
const variantClasses = {
89+
primary: 'bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white focus:ring-blue-500',
90+
secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-900 focus:ring-gray-500',
91+
outline: 'border-2 border-gray-300 hover:border-blue-600 bg-white hover:bg-blue-50 text-gray-700 hover:text-blue-600 focus:ring-blue-500',
92+
ghost: 'bg-transparent hover:bg-gray-100 text-gray-700 hover:text-gray-900 focus:ring-gray-500',
93+
danger: 'bg-red-600 hover:bg-red-700 text-white focus:ring-red-500'
94+
}
95+
96+
// Computed classes
97+
const buttonClasses = computed(() => [
98+
baseClasses,
99+
sizeClasses[props.size],
100+
variantClasses[props.variant],
101+
{
102+
'w-full': props.block,
103+
'rounded-xl': props.rounded || props.size === 'lg' || props.size === 'xl',
104+
'rounded-lg': !props.rounded && (props.size === 'md' || props.size === 'sm'),
105+
'rounded-md': !props.rounded && props.size === 'xs',
106+
'shadow-lg hover:shadow-xl transform hover:-translate-y-0.5': props.shadow && props.variant === 'primary',
107+
'shadow-md hover:shadow-lg': props.shadow && props.variant !== 'primary',
108+
'cursor-wait': props.loading
109+
}
110+
])
111+
112+
const handleClick = (event: Event) => {
113+
if (!props.disabled && !props.loading) {
114+
emit('click', event)
115+
}
116+
}
117+
</script>

0 commit comments

Comments
 (0)