monacousa-portal/design-mockups/components/core/NeumorphicCard.vue

215 lines
4.4 KiB
Vue

<template>
<div
:class="[
'neumorphic-card',
sizeClasses,
elevationClasses,
{
'cursor-pointer': clickable,
'transition-all duration-300': animated
}
]"
@click="handleClick"
>
<div v-if="$slots.header || title" class="neumorphic-card__header">
<slot name="header">
<h3 v-if="title" class="neumorphic-card__title">{{ title }}</h3>
<p v-if="subtitle" class="neumorphic-card__subtitle">{{ subtitle }}</p>
</slot>
</div>
<div class="neumorphic-card__content">
<slot></slot>
</div>
<div v-if="$slots.footer" class="neumorphic-card__footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
title?: string;
subtitle?: string;
size?: 'sm' | 'md' | 'lg' | 'xl';
elevation?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
clickable?: boolean;
animated?: boolean;
pressed?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
size: 'md',
elevation: 'md',
clickable: false,
animated: true,
pressed: false
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
const sizeClasses = computed(() => {
const sizes = {
sm: 'p-4',
md: 'p-6',
lg: 'p-8',
xl: 'p-10'
};
return sizes[props.size];
});
const elevationClasses = computed(() => {
if (props.pressed) return 'neumorphic-pressed';
const elevations = {
none: '',
sm: 'neumorphic-elevation-sm',
md: 'neumorphic-elevation-md',
lg: 'neumorphic-elevation-lg',
xl: 'neumorphic-elevation-xl'
};
return elevations[props.elevation];
});
const handleClick = (event: MouseEvent) => {
if (props.clickable) {
emit('click', event);
}
};
</script>
<style lang="scss" scoped>
@import '../../styles/neumorphic-system.scss';
.neumorphic-card {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
border-radius: $radius-xl;
position: relative;
overflow: hidden;
// Base elevation styles
&.neumorphic-elevation-sm {
box-shadow: $shadow-soft-sm;
}
&.neumorphic-elevation-md {
box-shadow: $shadow-soft-md;
}
&.neumorphic-elevation-lg {
box-shadow: $shadow-soft-lg;
}
&.neumorphic-elevation-xl {
box-shadow: $shadow-soft-xl;
}
&.neumorphic-pressed {
box-shadow: $shadow-inset-md;
background: linear-gradient(145deg, #e6e6e6, #ffffff);
}
// Hover effects for clickable cards
&.cursor-pointer {
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-soft-lg;
}
&:active {
transform: translateY(0);
box-shadow: $shadow-inset-sm;
}
}
// Header section
&__header {
padding-bottom: $space-4;
border-bottom: 1px solid rgba($neutral-300, 0.5);
margin-bottom: $space-4;
}
&__title {
font-family: $font-heading;
font-size: $text-xl;
font-weight: $font-semibold;
color: $neutral-800;
margin: 0;
line-height: $leading-tight;
}
&__subtitle {
font-family: $font-body;
font-size: $text-sm;
color: $neutral-600;
margin-top: $space-2;
line-height: $leading-normal;
}
// Content section
&__content {
font-family: $font-body;
color: $neutral-700;
line-height: $leading-relaxed;
}
// Footer section
&__footer {
padding-top: $space-4;
border-top: 1px solid rgba($neutral-300, 0.5);
margin-top: $space-4;
}
// Dark mode support
@include dark-mode {
background: linear-gradient(145deg, $neutral-800, $neutral-700);
&.neumorphic-elevation-sm {
box-shadow: $shadow-dark-soft-sm;
}
&.neumorphic-elevation-md {
box-shadow: $shadow-dark-soft-md;
}
&.neumorphic-elevation-lg {
box-shadow: $shadow-dark-soft-lg;
}
&.neumorphic-pressed {
box-shadow: $shadow-dark-inset-md;
background: linear-gradient(145deg, $neutral-900, $neutral-800);
}
.neumorphic-card__title {
color: $neutral-100;
}
.neumorphic-card__subtitle {
color: $neutral-400;
}
.neumorphic-card__content {
color: $neutral-300;
}
.neumorphic-card__header,
.neumorphic-card__footer {
border-color: rgba($neutral-600, 0.5);
}
}
// Responsive adjustments
@include responsive('sm') {
&.p-4 { padding: $space-5; }
&.p-6 { padding: $space-8; }
&.p-8 { padding: $space-10; }
&.p-10 { padding: $space-12; }
}
}
</style>