first big style pass, consolidate to mixins

This commit is contained in:
Matt Beaudoin
2026-05-15 20:00:29 -07:00
parent b7e8bab6d1
commit e6f159bbea
9 changed files with 98 additions and 154 deletions
+69
View File
@@ -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;
}
}
+1 -19
View File
@@ -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;
+6 -49
View File
@@ -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
View File
@@ -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')}
-1
View File
@@ -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;
+3 -16
View File
@@ -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;
+1 -19
View File
@@ -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;
+5 -16
View File
@@ -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;
+2 -22
View File
@@ -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 */}