Hwandji's picture
feat: initial HuggingFace Space deployment
4343907
raw
history blame
3.73 kB
<template>
<component
:is="tag"
:class="cardClasses"
v-bind="$attrs"
>
<!-- Header -->
<header v-if="title || subtitle || $slots.header" class="saap-card__header">
<slot name="header">
<div v-if="title || subtitle" class="saap-card__title-group">
<h3 v-if="title" class="saap-card__title">{{ title }}</h3>
<p v-if="subtitle" class="saap-card__subtitle">{{ subtitle }}</p>
</div>
</slot>
<div v-if="$slots.actions" class="saap-card__actions">
<slot name="actions" />
</div>
</header>
<!-- Content -->
<div :class="contentClasses">
<slot />
</div>
<!-- Footer -->
<footer v-if="$slots.footer" class="saap-card__footer">
<slot name="footer" />
</footer>
</component>
</template>
<script setup lang="ts">
import { computed, defineProps, withDefaults } from 'vue'
import type { CardProps } from '@/types'
interface Props extends CardProps {
tag?: 'div' | 'article' | 'section'
}
const props = withDefaults(defineProps<Props>(), {
tag: 'div',
hoverable: false,
padding: 'md',
shadow: 'sm',
border: true
})
// Computed
const cardClasses = computed(() => [
'saap-card',
`saap-card--padding-${props.padding}`,
`saap-card--shadow-${props.shadow}`,
{
'saap-card--hoverable': props.hoverable,
'saap-card--bordered': props.border
}
])
const contentClasses = computed(() => [
'saap-card__content',
{
'saap-card__content--no-padding': props.padding === 'none'
}
])
</script>
<style lang="scss" scoped>
@import '@/styles/mixins.scss';
.saap-card {
@include card-base;
// Padding variants
&--padding-none {
padding: 0;
}
&--padding-sm {
padding: var(--saap-space-4);
}
&--padding-md {
padding: var(--saap-space-6);
}
&--padding-lg {
padding: var(--saap-space-8);
}
// Shadow variants
&--shadow-none {
box-shadow: none;
}
&--shadow-sm {
box-shadow: var(--saap-shadow-sm);
}
&--shadow-md {
box-shadow: var(--saap-shadow-md);
}
&--shadow-lg {
box-shadow: var(--saap-shadow-lg);
}
// Border
&--bordered {
border: 1px solid var(--saap-neutral-200);
}
// Hoverable
&--hoverable {
@include card-hover;
}
}
.saap-card__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--saap-space-4);
margin-bottom: var(--saap-space-4);
&:last-child {
margin-bottom: 0;
}
}
.saap-card__title-group {
flex: 1;
min-width: 0;
}
.saap-card__title {
font-size: var(--saap-text-lg);
font-weight: var(--saap-font-semibold);
color: var(--saap-neutral-900);
margin: 0;
line-height: var(--saap-leading-snug);
}
.saap-card__subtitle {
font-size: var(--saap-text-sm);
color: var(--saap-neutral-600);
margin: var(--saap-space-1) 0 0 0;
line-height: var(--saap-leading-normal);
}
.saap-card__actions {
flex-shrink: 0;
display: flex;
align-items: center;
gap: var(--saap-space-2);
}
.saap-card__content {
flex: 1;
&--no-padding {
margin: calc(-1 * var(--saap-space-6));
margin-top: 0;
.saap-card--padding-sm & {
margin: calc(-1 * var(--saap-space-4));
margin-top: 0;
}
.saap-card--padding-lg & {
margin: calc(-1 * var(--saap-space-8));
margin-top: 0;
}
}
}
.saap-card__footer {
margin-top: var(--saap-space-4);
padding-top: var(--saap-space-4);
border-top: 1px solid var(--saap-neutral-200);
&:first-child {
margin-top: 0;
padding-top: 0;
border-top: none;
}
}
// Responsive adjustments
@include respond-to('sm') {
.saap-card__header {
align-items: center;
}
}
</style>