From 067bc4c795bfe401f32e81220077b3caed9fe5e4 Mon Sep 17 00:00:00 2001 From: Aleix Soler Date: Wed, 16 Jul 2025 19:18:48 +0200 Subject: [PATCH] chore: implement consistent BEM-like pattern --- styles/components/status-badge.css | 54 +++++++- styles/components/status-button.css | 146 +++++++++++++++++++++ styles/components/status-chip.css | 77 +++++++++++ styles/components/status-group.css | 107 ++++++++++++++- styles/components/status-item.css | 124 ++++++++++++++++++ styles/components/status-modal.css | 195 ++++++++++++++++++++++++++++ styles/globals.css | 134 +++++++++++++++++++ styles/index.css | 16 ++- styles/variables.css | 31 +++++ 9 files changed, 869 insertions(+), 15 deletions(-) create mode 100644 styles/components/status-button.css create mode 100644 styles/components/status-chip.css create mode 100644 styles/components/status-item.css create mode 100644 styles/components/status-modal.css create mode 100644 styles/globals.css create mode 100644 styles/variables.css diff --git a/styles/components/status-badge.css b/styles/components/status-badge.css index cd4afc0..6df1e18 100644 --- a/styles/components/status-badge.css +++ b/styles/components/status-badge.css @@ -1,27 +1,71 @@ -.status-badge-container { +/* Status Badge Component - BEM Pattern */ +.status-badge { display: inline-flex; flex-direction: column; align-items: center; border-radius: var(--radius-s); - /* backgroundColor and border need dynamic color values - handle with CSS custom properties or JS */ transition: all var(--anim-duration-moderate) var(--anim-motion-smooth); overflow: hidden; min-width: auto; max-width: none; } -.status-badge-item { +.status-badge--interactive { + cursor: pointer; +} + +.status-badge--interactive:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +.status-badge__item { display: flex; align-items: center; gap: var(--size-2-1); padding: var(--size-2-1) var(--size-2-3); transition: all var(--anim-duration-fast) ease; + width: 100%; } -.status-badge-icon { +.status-badge__item--active { + background: var(--interactive-accent); + color: var(--text-on-accent); +} + +.status-badge__item--hover:hover { + background: var(--background-modifier-hover); +} + +.status-badge__icon { transition: transform var(--anim-duration-fast) ease; + font-size: var(--font-ui-small); } -.status-badge-text { +.status-badge__text { font-weight: var(--font-weight-medium); + font-size: var(--font-ui-small); + white-space: nowrap; +} + +.status-badge__count { + background: var(--primary-color); + color: var(--text-on-accent); + font-size: var(--font-smaller); + font-weight: var(--font-semibold); + min-width: 12px; + height: 12px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-left: 1px; + box-shadow: var(--shadow-s); + border: 1px solid var(--background-primary); +} + +.status-badge__count--large { + min-width: 16px; + height: 16px; + font-size: var(--font-ui-smaller); } diff --git a/styles/components/status-button.css b/styles/components/status-button.css new file mode 100644 index 0000000..402dfa2 --- /dev/null +++ b/styles/components/status-button.css @@ -0,0 +1,146 @@ +/* Status Button Component - BEM Pattern */ +.status-btn { + display: inline-flex; + align-items: center; + gap: var(--size-2-1); + padding: var(--size-2-2) var(--size-2-3); + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-s); + background: var(--interactive-normal); + color: var(--text-normal); + cursor: pointer; + transition: all var(--anim-duration-fast) ease; + font-size: var(--font-ui-medium); + font-weight: var(--font-weight-medium); + text-decoration: none; + white-space: nowrap; +} + +.status-btn:hover { + background: var(--interactive-hover); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.status-btn:active { + background: var(--interactive-active); + transform: translateY(0); +} + +.status-btn--primary { + background: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +.status-btn--primary:hover { + background: var(--interactive-accent-hover); + border-color: var(--interactive-accent-hover); +} + +.status-btn--primary:active { + background: var(--interactive-accent-active); + border-color: var(--interactive-accent-active); +} + +.status-btn--secondary { + background: var(--background-secondary); + border-color: var(--background-modifier-border-hover); +} + +.status-btn--secondary:hover { + background: var(--background-modifier-hover); + border-color: var(--interactive-accent); +} + +.status-btn--danger { + background: var(--background-modifier-error); + color: var(--text-error); + border-color: var(--background-modifier-error); +} + +.status-btn--danger:hover { + background: var(--background-modifier-error-hover); + border-color: var(--text-error); +} + +.status-btn--small { + padding: var(--size-2-1) var(--size-2-2); + font-size: var(--font-ui-small); + min-height: 24px; +} + +.status-btn--large { + padding: var(--size-2-3) var(--size-4-2); + font-size: var(--font-ui-large); + min-height: 40px; +} + +.status-btn--full-width { + width: 100%; + justify-content: center; +} + +.status-btn--disabled { + opacity: 0.6; + cursor: not-allowed; + pointer-events: none; +} + +.status-btn--loading { + position: relative; + color: transparent; +} + +.status-btn--loading::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 16px; + height: 16px; + margin: -8px 0 0 -8px; + border: 2px solid currentColor; + border-radius: 50%; + border-top-color: transparent; + animation: statusBtnSpin 1s linear infinite; +} + +.status-btn__icon { + font-size: 1em; + line-height: 1; + transition: transform var(--anim-duration-fast) ease; +} + +.status-btn:hover .status-btn__icon { + transform: scale(1.1); +} + +.status-btn__text { + line-height: 1.2; +} + +.status-btn__badge { + background: var(--background-modifier-active); + color: var(--text-muted); + font-size: var(--font-ui-smaller); + font-weight: var(--font-weight-semibold); + padding: 1px var(--size-2-1); + border-radius: var(--radius-s); + min-width: 16px; + text-align: center; +} + +.status-btn--primary .status-btn__badge { + background: rgba(255, 255, 255, 0.2); + color: var(--text-on-accent); +} + +@keyframes statusBtnSpin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/styles/components/status-chip.css b/styles/components/status-chip.css new file mode 100644 index 0000000..ef2e432 --- /dev/null +++ b/styles/components/status-chip.css @@ -0,0 +1,77 @@ +/* Status Chip Component - BEM Pattern */ +.status-chip { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: var(--radius-xl); + background: var(--background-secondary); + font-size: 0.85em; + border: 1px solid var(--color-base-30); + gap: 4px; + transition: all var(--anim-duration-fast) ease; +} + +.status-chip:hover { + background: var(--background-modifier-hover); + transform: scale(1.02); +} + +.status-chip--template { + background: var(--background-primary-alt); + border-color: var(--color-base-40); +} + +.status-chip--removable { + padding-right: 4px; +} + +.status-chip--disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.status-chip__icon { + display: inline-block; + font-size: 1em; + line-height: 1; + transition: transform var(--anim-duration-fast) ease; +} + +.status-chip__text { + font-weight: var(--font-weight-medium); + color: var(--text-normal); + white-space: nowrap; +} + +.status-chip__color-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--dot-color, var(--color-base-30)); + flex-shrink: 0; +} + +.status-chip__remove-btn { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + border-radius: 50%; + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + transition: all var(--anim-duration-fast) ease; +} + +.status-chip__remove-btn:hover { + background: var(--background-modifier-active); + color: var(--text-normal); + transform: scale(1.1); +} + +.status-chip__remove-btn:active { + transform: scale(0.95); +} diff --git a/styles/components/status-group.css b/styles/components/status-group.css index 6436795..31e6659 100644 --- a/styles/components/status-group.css +++ b/styles/components/status-group.css @@ -1,12 +1,107 @@ +/* Status Group Component - BEM Pattern */ .status-group { - margin-bottom: 1.5em; + margin-bottom: var(--size-4-4); + background: var(--background-secondary); + border-radius: var(--radius-m); + border: var(--border-width) solid var(--background-modifier-border-hover); + overflow: hidden; + transition: all var(--anim-duration-fast) ease; } -.status-group-description { - font-size: 0.85em; - opacity: 0.7; +.status-group:last-child { + margin-bottom: 0; } -.status-group-items { - margin-left: 1em; +.status-group:hover { + border-color: var(--interactive-accent); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.status-group--collapsed { + overflow: hidden; +} + +.status-group--expanded { + overflow: visible; +} + +.status-group__header { + padding: var(--size-2-3) var(--size-4-2); + background: linear-gradient( + 90deg, + var(--interactive-accent-hover) 0%, + var(--interactive-accent) 100% + ); + color: var(--text-on-accent); + font-size: var(--font-ui-smaller); + font-weight: var(--font-weight-medium); + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: var(--border-width) solid var(--background-modifier-border); + cursor: pointer; + transition: all var(--anim-duration-fast) ease; +} + +.status-group__header:hover { + background: linear-gradient( + 90deg, + var(--interactive-accent) 0%, + var(--interactive-accent-hover) 100% + ); +} + +.status-group__title { + text-transform: capitalize; + letter-spacing: 0.3px; + font-weight: var(--font-weight-semibold); +} + +.status-group__description { + font-size: var(--font-ui-smaller); + color: var(--text-muted); + margin-top: var(--size-2-1); + opacity: 0.8; + line-height: var(--line-height-tight); +} + +.status-group__count { + background: rgba(255, 255, 255, 0.2); + color: var(--text-on-accent); + font-size: var(--font-ui-smaller); + font-weight: var(--font-weight-semibold); + padding: var(--size-2-1) var(--size-2-3); + border-radius: var(--radius-s); + min-width: var(--size-4-3); + text-align: center; + backdrop-filter: blur(4px); +} + +.status-group__items { + padding: var(--size-2-3); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: var(--size-2-2); + background: var(--background-primary); +} + +.status-group__toggle { + background: none; + border: none; + color: var(--text-on-accent); + cursor: pointer; + font-size: var(--font-ui-medium); + padding: var(--size-2-1); + border-radius: var(--radius-s); + transition: all var(--anim-duration-fast) ease; +} + +.status-group__toggle:hover { + background: rgba(255, 255, 255, 0.1); + transform: scale(1.1); +} + +.status-group__toggle--expanded { + transform: rotate(180deg); } diff --git a/styles/components/status-item.css b/styles/components/status-item.css new file mode 100644 index 0000000..28dc593 --- /dev/null +++ b/styles/components/status-item.css @@ -0,0 +1,124 @@ +/* Status Item Component - BEM Pattern */ +.status-item { + display: flex; + flex-direction: column; + gap: var(--size-2-1); + padding: var(--size-2-2) var(--size-2-3); + background: var(--background-modifier-hover); + border-radius: var(--radius-s); + border: var(--border-width) solid var(--background-modifier-border); + transition: all var(--anim-duration-fast) ease; + min-width: 0; + cursor: pointer; +} + +.status-item:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-color: var(--interactive-accent); +} + +.status-item--active { + background: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +.status-item--disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.status-item--disabled:hover { + transform: none; + box-shadow: none; + border-color: var(--background-modifier-border); +} + +.status-item__header { + display: flex; + align-items: center; + gap: var(--size-2-2); +} + +.status-item__icon { + font-size: var(--font-ui-medium); + flex-shrink: 0; + transition: all var(--anim-duration-fast) ease; +} + +.status-item__name { + font-size: var(--font-ui-small); + font-weight: var(--font-weight-medium); + color: var(--text-normal); + flex: 1; +} + +.status-item__description { + color: var(--text-muted); + font-size: var(--font-ui-smaller); + line-height: 1.3; + background: var(--background-primary-alt); + padding: var(--size-2-1) var(--size-2-2); + border-radius: var(--radius-s); + border-left: 2px solid var(--interactive-accent); + font-style: italic; + opacity: 0.8; + word-wrap: break-word; + hyphens: auto; +} + +.status-item__count { + background: var(--background-modifier-border); + color: var(--text-muted); + font-size: var(--font-ui-smaller); + font-weight: var(--font-weight-medium); + padding: var(--size-2-1) var(--size-2-2); + border-radius: var(--radius-s); + min-width: 16px; + text-align: center; +} + +.status-item__actions { + display: flex; + gap: var(--size-2-1); + margin-top: var(--size-2-1); +} + +.status-item__action { + background: transparent; + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-s); + padding: var(--size-2-1) var(--size-2-2); + color: var(--text-muted); + cursor: pointer; + transition: all var(--anim-duration-fast) ease; + font-size: var(--font-ui-smaller); +} + +.status-item__action:hover { + background: var(--background-modifier-active); + color: var(--text-normal); + border-color: var(--interactive-accent); +} + +.status-item__action--primary { + background: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +.status-item__action--primary:hover { + background: var(--interactive-accent-hover); + border-color: var(--interactive-accent-hover); +} + +.status-item__action--danger { + color: var(--text-error); + border-color: var(--background-modifier-error); +} + +.status-item__action--danger:hover { + background: var(--background-modifier-error-hover); + border-color: var(--text-error); +} diff --git a/styles/components/status-modal.css b/styles/components/status-modal.css new file mode 100644 index 0000000..121633d --- /dev/null +++ b/styles/components/status-modal.css @@ -0,0 +1,195 @@ +/* Status Modal Component - BEM Pattern */ +.status-modal { + position: fixed; + left: 50%; + transform: translateX(-50%); + background: var(--background-primary); + border: var(--border-width) solid var(--background-modifier-border); + border-radius: var(--radius-l); + box-shadow: var(--shadow-l); + z-index: var(--layer-tooltip); + min-width: 280px; + max-width: 360px; + animation: statusModalIn 0.15s ease-out; + backdrop-filter: blur(8px); +} + +.status-modal--position-bottom { + top: calc(100% + 8px); +} + +.status-modal--position-top { + bottom: calc(100% + 8px); +} + +.status-modal__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--size-4-4) var(--size-4-4) var(--size-4-2) var(--size-4-4); + border-bottom: var(--border-width) solid var(--background-modifier-border); +} + +.status-modal__title { + font-size: var(--font-ui-medium); + font-weight: var(--font-semibold); + color: var(--text-normal); +} + +.status-modal__total { + background: var(--background-modifier-hover); + color: var(--text-muted); + font-size: var(--font-smallest); + font-weight: var(--font-medium); + padding: var(--size-2-1) var(--size-2-3); + border-radius: var(--radius-m); +} + +.status-modal__content { + padding: var(--size-4-2); + max-height: 400px; + overflow-y: auto; +} + +.status-modal__group { + margin-bottom: var(--size-4-4); +} + +.status-modal__group:last-child { + margin-bottom: 0; +} + +.status-modal__group-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--size-2-3); +} + +.status-modal__group-name { + font-size: var(--font-ui-small); + font-weight: var(--font-semibold); + color: var(--text-normal); + text-transform: capitalize; +} + +.status-modal__group-count { + background: var(--background-modifier-hover); + color: var(--text-muted); + font-size: var(--font-smallest); + font-weight: var(--font-medium); + padding: var(--size-2-1) var(--size-2-2); + border-radius: var(--radius-s); + min-width: 16px; + text-align: center; +} + +.status-modal__item-list { + display: flex; + flex-direction: column; + gap: var(--size-2-2); +} + +.status-modal__item { + display: flex; + align-items: center; + gap: var(--size-4-2); + padding: var(--size-2-3) var(--size-4-2); + background: color-mix(in srgb, var(--item-color) 8%, transparent); + border: var(--border-width) solid + color-mix(in srgb, var(--item-color) 20%, transparent); + border-radius: var(--radius-s); + transition: all var(--anim-duration-fast) ease; + cursor: pointer; +} + +.status-modal__item:hover { + background: color-mix(in srgb, var(--item-color) 12%, transparent); + border-color: color-mix(in srgb, var(--item-color) 35%, transparent); + transform: translateX(2px); +} + +.status-modal__item-icon { + font-size: var(--font-ui-medium); + flex-shrink: 0; +} + +.status-modal__item-info { + display: flex; + flex-direction: column; + gap: var(--size-2-1); + min-width: 0; + flex: 1; +} + +.status-modal__item-name { + font-size: var(--font-ui-small); + font-weight: var(--font-medium); + color: var(--text-normal); +} + +.status-modal__item-description { + font-size: var(--font-smallest); + color: var(--text-muted); + line-height: var(--line-height-tight); +} + +.status-modal__arrow { + position: absolute; + left: 50%; + transform: translateX(-50%); + width: 10px; + height: 10px; + background: var(--background-primary); + border: var(--border-width) solid var(--background-modifier-border); + rotate: 45deg; +} + +.status-modal--position-bottom .status-modal__arrow { + top: -5px; + border-bottom: none; + border-right: none; +} + +.status-modal--position-top .status-modal__arrow { + bottom: -5px; + border-top: none; + border-left: none; +} + +.status-modal__empty { + padding: var(--size-4-4) var(--size-4-3); + text-align: center; + color: var(--text-muted); + font-size: var(--font-ui-smaller); + background: var(--background-primary); + border: var(--border-width) solid var(--background-modifier-border); + border-radius: var(--radius-l); + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1000; + pointer-events: none; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--size-2-2); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); +} + +.status-modal__empty-icon { + font-size: var(--font-ui-large); + opacity: 0.6; +} + +@keyframes statusModalIn { + from { + opacity: 0; + transform: translateX(-50%) scale(0.95); + } + to { + opacity: 1; + transform: translateX(-50%) scale(1); + } +} diff --git a/styles/globals.css b/styles/globals.css new file mode 100644 index 0000000..40d4828 --- /dev/null +++ b/styles/globals.css @@ -0,0 +1,134 @@ +/* Global utility classes */ +.status-plugin-hidden { + display: none !important; +} + +.status-plugin-visible { + display: block !important; +} + +.status-plugin-flex { + display: flex !important; +} + +.status-plugin-inline-flex { + display: inline-flex !important; +} + +.status-plugin-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.status-plugin-no-wrap { + white-space: nowrap; +} + +.status-plugin-loading { + opacity: 0.6; + pointer-events: none; +} + +.status-plugin-disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* Animation keyframes */ +@keyframes statusFadeIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes statusSlideIn { + from { + opacity: 0; + transform: scale(0.8) translateY(4px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes statusPulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +@keyframes statusSpin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Responsive design utilities */ +@media (max-width: 768px) { + .status-plugin-mobile-hidden { + display: none !important; + } + + .status-plugin-mobile-full-width { + width: 100% !important; + } + + .status-plugin-mobile-stack { + flex-direction: column !important; + } +} + +/* Theme-specific overrides */ +.theme-dark { + --status-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); + --status-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4); + --status-shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.5); +} + +.theme-light { + --status-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); + --status-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.15); + --status-shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.2); +} + +/* Focus styles for accessibility */ +.status-plugin-focus-ring:focus { + outline: 2px solid var(--interactive-accent); + outline-offset: 2px; +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .status-chip, + .status-badge, + .status-item, + .status-btn { + border-width: 2px; + } +} + +/* Reduced motion support */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/styles/index.css b/styles/index.css index 2f3fe1f..1cb860f 100644 --- a/styles/index.css +++ b/styles/index.css @@ -1,17 +1,25 @@ /* * Note Status Plugin for Obsidian - * Main CSS file importing all components + * Main CSS file with BEM-like naming conventions * * Author: Aleix Soler */ -@import "components/setting-item.css"; + +/* Import component stylesheets */ +@import "variables.css"; +@import "globals.css"; +@import "components/status-chip.css"; +@import "components/status-badge.css"; +@import "components/status-item.css"; +@import "components/status-button.css"; +@import "components/status-modal.css"; +@import "components/status-group.css"; @import "components/template-status-chip.css"; @import "components/template-item.css"; @import "components/custom-status-item.css"; +@import "components/setting-item.css"; @import "components/quick-commands-settings.css"; -@import "components/status-group.css"; @import "components/status-bar-enable-button.css"; -@import "components/status-badge.css"; @import "components/status-bar-group.css"; @import "components/status-bar.css"; @import "components/collapsible-counter.css"; diff --git a/styles/variables.css b/styles/variables.css new file mode 100644 index 0000000..a14b50d --- /dev/null +++ b/styles/variables.css @@ -0,0 +1,31 @@ +:root { + /* Status color palette */ + --status-color-primary: var(--interactive-accent); + --status-color-success: var(--color-green); + --status-color-warning: var(--color-orange); + --status-color-error: var(--color-red); + --status-color-info: var(--color-blue); + --status-color-neutral: var(--color-base-60); + + /* Status spacing */ + --status-spacing-xs: 2px; + --status-spacing-sm: 4px; + --status-spacing-md: 8px; + --status-spacing-lg: 12px; + --status-spacing-xl: 16px; + + /* Status borders */ + --status-border-radius: var(--radius-s); + --status-border-width: 1px; + --status-border-color: var(--background-modifier-border); + + /* Status shadows */ + --status-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); + --status-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.15); + --status-shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.2); + + /* Status animations */ + --status-transition-fast: all 0.15s ease; + --status-transition-normal: all 0.2s ease; + --status-transition-slow: all 0.3s ease; +}