chore: initial commit
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
<!--
|
||||
/**
|
||||
* Action Card Component - Platform Neutral Action Card
|
||||
*
|
||||
* Reusable card component for displaying actions
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="action-card" @click="handleClick" :class="{ loading: loading }">
|
||||
<div class="card-content">
|
||||
<div class="card-icon">{{ icon }}</div>
|
||||
<div class="card-text">
|
||||
<h3 class="card-title">{{ title }}</h3>
|
||||
<p class="card-description">{{ description }}</p>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
<span v-if="loading" class="loading-spinner">⟳</span>
|
||||
<span v-else class="arrow-icon">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-facing-decorator'
|
||||
|
||||
@Component
|
||||
export default class ActionCard extends Vue {
|
||||
@Prop({ required: true })
|
||||
icon!: string
|
||||
|
||||
@Prop({ required: true })
|
||||
title!: string
|
||||
|
||||
@Prop({ required: true })
|
||||
description!: string
|
||||
|
||||
@Prop({ default: false })
|
||||
loading!: boolean
|
||||
|
||||
handleClick(): void {
|
||||
if (!this.loading) {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.action-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-card.loading {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
flex-shrink: 0;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.action-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,202 @@
|
||||
<!--
|
||||
/**
|
||||
* Status Card Component - Platform Neutral Status Display
|
||||
*
|
||||
* Reusable card component for displaying system status
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="status-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">System Status</h3>
|
||||
<button class="refresh-button" @click="refreshStatus" :disabled="isRefreshing">
|
||||
<span v-if="isRefreshing" class="loading-spinner">⟳</span>
|
||||
<span v-else class="refresh-icon">🔄</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="status-items">
|
||||
<div
|
||||
v-for="item in statusItems"
|
||||
:key="item.label"
|
||||
class="status-item"
|
||||
:class="`status-${item.status}`"
|
||||
>
|
||||
<div class="status-label">{{ item.label }}</div>
|
||||
<div class="status-value">
|
||||
<span class="status-indicator" :class="`indicator-${item.status}`"></span>
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-facing-decorator'
|
||||
|
||||
interface StatusItem {
|
||||
label: string
|
||||
value: string
|
||||
status: 'success' | 'error' | 'warning' | 'info'
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class StatusCard extends Vue {
|
||||
@Prop({ required: true })
|
||||
statusItems!: StatusItem[]
|
||||
|
||||
@Prop({ default: false })
|
||||
isRefreshing!: boolean
|
||||
|
||||
refreshStatus(): void {
|
||||
this.$emit('refresh')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.refresh-button:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.refresh-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.refresh-icon, .loading-spinner {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.status-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.status-item.status-success {
|
||||
border-left-color: #4caf50;
|
||||
}
|
||||
|
||||
.status-item.status-error {
|
||||
border-left-color: #f44336;
|
||||
}
|
||||
|
||||
.status-item.status-warning {
|
||||
border-left-color: #ff9800;
|
||||
}
|
||||
|
||||
.status-item.status-info {
|
||||
border-left-color: #2196f3;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.status-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.indicator-success {
|
||||
background: #4caf50;
|
||||
}
|
||||
|
||||
.indicator-error {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.indicator-warning {
|
||||
background: #ff9800;
|
||||
}
|
||||
|
||||
.indicator-info {
|
||||
background: #2196f3;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.status-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user