forked from BornDeleuze/coop-cloud-front
first big style pass, consolidate to mixins
This commit is contained in:
@@ -28,6 +28,16 @@
|
||||
padding: $spacing-xl;
|
||||
}
|
||||
|
||||
// Ensure cards occupy consistent vertical space and can stretch horizontally
|
||||
@mixin card-dimensions($min-width: 320px, $min-height: 180px) {
|
||||
min-width: $min-width;
|
||||
min-height: $min-height;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/// Standard vertical stack for card-style list rows (dashboard recent apps, server detail apps, etc.)
|
||||
@mixin card-list-vertical($gap: $spacing-md) {
|
||||
display: flex;
|
||||
@@ -141,4 +151,63 @@
|
||||
font-weight: $font-weight-semibold;
|
||||
background-color: rgba($color, 0.1);
|
||||
// color: darken($color, 20%);
|
||||
}
|
||||
|
||||
// Compact chip used for stat chips and small interactive chips
|
||||
@mixin chip(
|
||||
$bg: white,
|
||||
$border-color: $border-color,
|
||||
$radius: $radius-md,
|
||||
$padding: $spacing-md $spacing-lg,
|
||||
$gap: $spacing-md,
|
||||
$hover-translate: -2px,
|
||||
$hover-shadow: $shadow-sm,
|
||||
$font-size: $font-size-base
|
||||
) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $gap;
|
||||
padding: $padding;
|
||||
background: $bg;
|
||||
border: 2px solid $border-color;
|
||||
border-radius: $radius;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
font-size: $font-size;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: $primary;
|
||||
transform: translateY($hover-translate);
|
||||
box-shadow: $hover-shadow;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable action button styles; accepts border width and radius
|
||||
@mixin action-btn($border-width: 1px, $radius: $radius-sm, $padding: $spacing-xs $spacing-md, $font-size: $font-size-sm) {
|
||||
background: none;
|
||||
border: $border-width solid $border-color;
|
||||
padding: $padding;
|
||||
border-radius: $radius;
|
||||
cursor: pointer;
|
||||
font-size: $font-size;
|
||||
color: $text-primary;
|
||||
font-weight: $font-weight-medium;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($primary, 0.05);
|
||||
color: $text-primary;
|
||||
transform: translateY(-1px);
|
||||
border-color: $primary;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@@ -47,25 +47,7 @@
|
||||
|
||||
// Action buttons
|
||||
.action-btn {
|
||||
padding: $spacing-sm $spacing-lg;
|
||||
border: 2px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
background: white;
|
||||
color: $text-primary;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
@include action-btn(2px, $radius-md, $spacing-sm $spacing-lg, $font-size-sm);
|
||||
|
||||
&.primary {
|
||||
background: $primary;
|
||||
|
||||
@@ -21,31 +21,12 @@
|
||||
}
|
||||
|
||||
.stat-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
background: white;
|
||||
border: 2px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
font-size: $font-size-base;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: $primary;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@include chip();
|
||||
|
||||
&.active {
|
||||
border-color: $primary;
|
||||
background: rgba($primary, 0.05);
|
||||
border-color: $primary-dark;
|
||||
background: $bg-primary;
|
||||
box-shadow: 0 0 0 3px rgba($primary-dark, 0.08);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
@@ -63,23 +44,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.reset-filters-btn {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
background: $bg-secondary;
|
||||
border: 2px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $text-secondary;
|
||||
|
||||
&:hover {
|
||||
background: white;
|
||||
color: $text-primary;
|
||||
border-color: $primary;
|
||||
}
|
||||
}
|
||||
/* reset-filters-btn removed — outline on stat-chip indicates active filters */
|
||||
|
||||
// Apps table specific styles
|
||||
.apps-table-container {
|
||||
@@ -181,15 +146,7 @@
|
||||
gap: $spacing-sm;
|
||||
|
||||
.action-btn {
|
||||
background: none;
|
||||
border: 1px solid $border-color;
|
||||
padding: $spacing-xs $spacing-md;
|
||||
border-radius: $radius-sm;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-primary;
|
||||
font-weight: $font-weight-medium;
|
||||
transition: all $transition-base;
|
||||
@include action-btn(1px, $radius-sm, $spacing-xs $spacing-md, $font-size-sm);
|
||||
|
||||
&:hover {
|
||||
background-color: $primary;
|
||||
|
||||
+11
-12
@@ -94,19 +94,19 @@ export const Apps: React.FC = () => {
|
||||
setShowUpgradesOnly(false);
|
||||
};
|
||||
|
||||
const toggleUpgrades = () => setShowUpgradesOnly(prev => !prev);
|
||||
const toggleChaos = () => setShowChaosOnly(prev => !prev);
|
||||
|
||||
// Show only apps that need upgrades
|
||||
const filterByUpgrades = () => {
|
||||
setShowUpgradesOnly(true);
|
||||
setFilterStatus('all');
|
||||
const toggleUpgrades = () => {
|
||||
setShowUpgradesOnly(!showUpgradesOnly);
|
||||
if (!showUpgradesOnly) {
|
||||
setFilterStatus('all');
|
||||
setShowChaosOnly(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Show only chaos apps
|
||||
const filterByChaos = () => {
|
||||
setFilterStatus('chaos');
|
||||
setShowUpgradesOnly(false);
|
||||
const toggleChaos = () => {
|
||||
const newChaosState = !showChaosOnly;
|
||||
setShowChaosOnly(newChaosState);
|
||||
setFilterStatus(newChaosState ? 'chaos' : 'all');
|
||||
if (newChaosState) setShowUpgradesOnly(false);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
@@ -150,7 +150,6 @@ export const Apps: React.FC = () => {
|
||||
<span className="stat-label">Apps</span>
|
||||
<span className="stat-value">{stats.total}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="stat-chip"
|
||||
onClick={() => navigate('/servers')}
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
flex: 1;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 $spacing-xs;
|
||||
font-size: $font-size-lg;
|
||||
color: $text-primary;
|
||||
font-weight: $font-weight-semibold;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// Servers grid
|
||||
.recipes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: $spacing-xl;
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
// Server card
|
||||
.recipe-card {
|
||||
@include card;
|
||||
@include card-dimensions(320px, 180px);
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 2fr auto 0.9fr;
|
||||
transition: transform $transition-base, box-shadow $transition-base;
|
||||
@@ -110,21 +111,7 @@
|
||||
gap: $spacing-sm;
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
border: 2px solid $border-color;
|
||||
background: none;
|
||||
color: $text-primary;
|
||||
border-radius: $radius-md;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($primary, 0.1);
|
||||
border-color: $primary;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
@include action-btn(2px, $radius-md, $spacing-sm $spacing-md, $font-size-sm);
|
||||
|
||||
&.primary {
|
||||
background-color: $primary;
|
||||
|
||||
@@ -47,25 +47,7 @@
|
||||
|
||||
// Action buttons (shared with App view, could be moved to global)
|
||||
.action-btn {
|
||||
padding: $spacing-sm $spacing-lg;
|
||||
border: 2px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
background: white;
|
||||
color: $text-primary;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
@include action-btn(2px, $radius-md, $spacing-sm $spacing-lg, $font-size-sm);
|
||||
|
||||
&.primary {
|
||||
background: $primary;
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
// Servers grid
|
||||
.servers-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: $spacing-xl;
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
align-items: stretch;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -27,6 +29,7 @@
|
||||
.server-card {
|
||||
@include card;
|
||||
@include card-hover-lift(-4px, $shadow-xl);
|
||||
@include card-dimensions(320px, 180px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
@@ -116,21 +119,7 @@
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
border: 2px solid $border-color;
|
||||
background: none;
|
||||
color: $text-primary;
|
||||
border-radius: $radius-md;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($primary, 0.1);
|
||||
border-color: $primary;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
@include action-btn(2px, $radius-md, $spacing-sm $spacing-md, $font-size-sm);
|
||||
|
||||
&.primary {
|
||||
background-color: $primary;
|
||||
|
||||
@@ -102,11 +102,7 @@ export const Servers: React.FC = () => {
|
||||
return filtered;
|
||||
}, [servers, searchTerm, sortBy, showUpgradesOnly]);
|
||||
|
||||
const resetFilters = () => {
|
||||
setSearchTerm('');
|
||||
setSortBy('name');
|
||||
setShowUpgradesOnly(false);
|
||||
};
|
||||
// resetFilters removed; clearing filters is implicit via toggles
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -141,15 +137,6 @@ const resetFilters = () => {
|
||||
|
||||
{/* Compact Stats Row */}
|
||||
<div className="stats-row">
|
||||
<button
|
||||
className="stat-chip"
|
||||
onClick={resetFilters}
|
||||
title="Click to reset filters"
|
||||
>
|
||||
<span className="stat-label">Servers</span>
|
||||
<span className="stat-value">{stats.totalServers}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="stat-chip"
|
||||
onClick={() => navigate('/apps')}
|
||||
@@ -178,14 +165,7 @@ const resetFilters = () => {
|
||||
<span className="stat-value">{stats.totalChaos}</span>
|
||||
</button>
|
||||
|
||||
{(searchTerm || sortBy !== 'name' || showUpgradesOnly) && (
|
||||
<button
|
||||
className="reset-filters-btn"
|
||||
onClick={resetFilters}
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
{/* Clear filters button removed — use stat-chip outlines for active filters */}
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
|
||||
Reference in New Issue
Block a user