diff --git a/src/lib/components/button/button.component.scss b/src/lib/components/button/button.component.scss index 6e3e28a..c5013bc 100644 --- a/src/lib/components/button/button.component.scss +++ b/src/lib/components/button/button.component.scss @@ -4,9 +4,133 @@ align-items: center; justify-content: center; padding: 0px 0.75rem; + min-height: 2rem; + border-radius: 4px; + + --border-color-rgb: var(--ui-color-background-rgb); + --text-color-rgb: var(--ui-color-text-rgb); + --background-color-rgb: var(--ui-color-background-rgb); + --border-color-alpha: 0; + --background-color-alpha: 0; + --background-hover-rgb: var(--ui-color-content-bg-rgb); + + --border-color: rgb(var(--border-color-rgb)); + --text-color: rgb(var(--text-color-rgb)); + --background-color: rgb(var(--background-color-rgb)); + + + background: rgba(var(--background-color-rgb), var(--background-color-alpha)); + color: var(--text-color); + border: 1px solid rgba(var(--border-color-rgb), var(--border-color-alpha)); + + &[size="small"] { + font-size: 0.85rem; + padding: 0px 0.5rem; + min-height: 1.5rem; + } + + &[size="large"] { + font-size: 1.2rem; + padding: 0px 1rem; + min-height: 2.5rem; + } + + &[theme="primary"] { + --background-color-alpha: 1; + --border-color-alpha: 1; + --background-color-rgb: var(--ui-color-primary-rgb); + --background-hover-rgb: var(--ui-color-primary-hard-rgb); + --text-color-rgb: var(--ui-color-primary-contrast-rgb); + --text-hover-rgb: var(--ui-color-background-rgb); + --border-color-rgb: var(--ui-color-primary-rgb); + } + + &[theme="secondary"] { + --background-color-alpha: 1; + --border-color-alpha: 1; + --background-color-rgb: var(--ui-color-secondary-rgb); + --background-hover-rgb: var(--ui-color-secondary-hard-rgb); + --text-color-rgb: var(--ui-color-secondary-contrast-rgb); + --text-hover-rgb: var(--ui-color-background-rgb); + --border-color-rgb: var(--ui-color-secondary-rgb); + } + + &[theme="danger"] { + --background-color-alpha: 1; + --border-color-alpha: 1; + --background-color-rgb: var(--ui-color-danger-rgb); + --background-hover-rgb: var(--ui-color-danger-hard-rgb); + --text-color-rgb: var(--ui-color-danger-contrast-rgb); + --text-hover-rgb: var(--ui-color-background-rgb); + --border-color-rgb: var(--ui-color-danger-rgb); + } + + &[theme="warning"] { + --background-color-alpha: 1; + --border-color-alpha: 1; + --background-color-rgb: var(--ui-color-warning-rgb); + --background-hover-rgb: var(--ui-color-warning-hard-rgb); + --text-color-rgb: var(--ui-color-warning-contrast-rgb); + --text-hover-rgb: var(--ui-color-background-rgb); + --border-color-rgb: var(--ui-color-warning-rgb); + } + + &[theme="info"] { + --background-color-alpha: 1; + --border-color-alpha: 1; + --background-color-rgb: var(--ui-color-info-rgb); + --background-hover-rgb: var(--ui-color-info-hard-rgb); + --text-color-rgb: var(--ui-color-info-contrast-rgb); + --text-hover-rgb: var(--ui-color-background-rgb); + --border-color-rgb: var(--ui-color-info-rgb); + } + + &[theme="success"] { + --background-color-alpha: 1; + --border-color-alpha: 1; + --background-color-rgb: var(--ui-color-success-rgb); + --background-hover-rgb: var(--ui-color-success-hard-rgb); + --text-hover-rgb: var(--ui-color-background-rgb); + --border-color-rgb: var(--ui-color-success-rgb); + &:not(&[outlined=true]) { + --text-color-rgb: var(--ui-color-success-contrast-rgb); + } + } + + &[outlined="true"] { + --background-color-alpha: 0; + --border-color-alpha: 1; + --text-color-rgb: var(--border-color-rgb); + &[theme="default"] { + --text-color-rgb: var(--ui-color-text-rgb); + --border-color-rgb: var(--ui-color-text-rgb); + --background-hover-rgb: var(--ui-color-text-rgb); + --text-hover-rgb: var(--ui-color-background-rgb); + } + } + + &.active, &.focus, &:active, &:hover{ - background: var(--ui-color-button-hover, rgba(255, 255, 255, 0.1)); + --background-color-alpha: 1; + --border-color-alpha: 1; + --background-color-rgb: var(--background-hover-rgb); + --border-color-rgb: var(--background-hover-rgb); + --text-color-rgb: var(--text-hover-rgb); + + &[outlined=true] { + --text-color-rgb: var(--text-hover-rgb); + } + } + + + & ::ng-deep ~ ui-button{ + margin-left: 0.25rem; + } + +} +::ng-deep ui-header > ui-button{ + border-radius: 0px !important; } diff --git a/src/lib/components/button/button.component.ts b/src/lib/components/button/button.component.ts index 8961bfb..d76db47 100644 --- a/src/lib/components/button/button.component.ts +++ b/src/lib/components/button/button.component.ts @@ -1,4 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, HostBinding, Input } from '@angular/core'; + +export type ButtonTheme = 'default' | 'primary' | 'secondary' | 'danger' | 'warning' | 'info' | 'success'; @Component({ selector: 'ui-button', @@ -7,5 +9,16 @@ import { Component } from '@angular/core'; styleUrl: './button.component.scss' }) export class ButtonComponent { + @Input() + @HostBinding('attr.theme') + theme: ButtonTheme = 'default'; + + @Input() + @HostBinding('attr.outlined') + outlined: boolean | string = false; + + @Input() + @HostBinding('attr.size') + size: 'small' | 'large' | 'default' = 'default'; } diff --git a/src/lib/components/card/card-footer/card-footer.component.scss b/src/lib/components/card/card-footer/card-footer.component.scss index 74fcf37..dd9a842 100644 --- a/src/lib/components/card/card-footer/card-footer.component.scss +++ b/src/lib/components/card/card-footer/card-footer.component.scss @@ -1,11 +1,7 @@ :host { - display: block; - padding: 0 var(--ui-spacing-md, 16px) var(--ui-spacing-md, 16px); - border-top: 1px solid var(--ui-color-menu-border, #d0d0d0); - background: var(--ui-color-menu-active, rgba(0, 0, 0, 0.1)); - color: var(--ui-color-menu-text, #ffffff); - - &:empty { - display: none; - } -} \ No newline at end of file + padding: 0.5rem; + display: flex; + &:empty{ + display: none; + } +} diff --git a/src/lib/modules/form/components/input/input.component.html b/src/lib/modules/form/components/input/input.component.html index 53c650d..36fc017 100644 --- a/src/lib/modules/form/components/input/input.component.html +++ b/src/lib/modules/form/components/input/input.component.html @@ -1 +1,18 @@ -

input works!

+
+ +
+ + +
+ +
diff --git a/src/lib/modules/form/components/input/input.component.scss b/src/lib/modules/form/components/input/input.component.scss index e69de29..003787e 100644 --- a/src/lib/modules/form/components/input/input.component.scss +++ b/src/lib/modules/form/components/input/input.component.scss @@ -0,0 +1,94 @@ +:host { + display: block; + width: 100%; + + --background-color-rgb: var(--ui-color-input-bg-rgb, 255, 255, 255); + --accent-color-rgb: var(--ui-color-input-label-focus-rgb, 0, 123, 255); + --text-color-rgb: var(--ui-color-input-text-rgb, 33, 37, 41); + + --background-color: var(--ui-color-input-bg, rgb(var(--background-color-rgb))); + --accent-color: var(--ui-color-input-label-focus, rgb(var(--accent-color-rgb))); + --text-color: var(--ui-color-input-text, rgb(var(--text-color-rgb))); + + background-color: var(--background-color); + box-shadow: 0px 0px 0px 0px var(--background-color), 0px 0px 0px 0px var(--accent-color); + transition: all 0.2s ease-in-out; + + position: relative; + display: flex; + align-items: stretch; + gap: 0.5rem; + cursor: text; + min-height: 2rem; + &.invalid{ + --accent-color: var(--ui-color-input-label-invalid, #dc3545); + } + + &.active:not(.disabled), + &.focused{ + box-shadow: 0px 0px 0px 9px var(--background-color), 0px 0px 0px 10px var(--accent-color); + } + &.hovered { + box-shadow: 0px 0px 0px 0px var(--background-color), 0px 0px 0px 1px var(--accent-color); + } + &.active:not(.disabled), + &.focused, + &.hovered { + --background-color: var(--ui-color-input-bg-hover, #313b44); + } + &.disabled { + opacity: 0.6; + cursor: not-allowed; + } + .ui-input-label { + position: absolute; + left: 0.5rem; + top: 50%; + transform: translateY(-50%); + font-size: 1rem; + color: rgba(var(--accent-color-rgb), 0.4); + pointer-events: none; + transition: all 0.2s ease-in-out; + background-color: transparent; + border-radius: 6px; + padding: 0 0.25rem; + white-space: nowrap; + + &.floating { + top: 0; + color: var(--accent-color); + transform: translateY(-50%); + font-size: 0.7rem; + background-color: var(--background-color); + } + } + + ::ng-deep { + > div { + display: flex; + > [place] { + display: flex; + } + } + } +} + +.ui-input-field { + flex: 1; + padding: 0.5rem 0.25rem; + border: none; + outline: none; + background: transparent; + font-size: 1rem; + line-height: 1.5; + color: var(--ui-color-input-text, #212529); + + &::placeholder { + color: transparent; + } + + &:disabled { + cursor: not-allowed; + } +} + diff --git a/src/lib/modules/form/components/input/input.component.spec.ts b/src/lib/modules/form/components/input/input.component.spec.ts deleted file mode 100644 index 9f17839..0000000 --- a/src/lib/modules/form/components/input/input.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { InputComponent } from './input.component'; - -describe('InputComponent', () => { - let component: InputComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [InputComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(InputComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/lib/modules/form/components/input/input.component.ts b/src/lib/modules/form/components/input/input.component.ts index 18ed28a..c4dd618 100644 --- a/src/lib/modules/form/components/input/input.component.ts +++ b/src/lib/modules/form/components/input/input.component.ts @@ -1,11 +1,134 @@ -import { Component } from '@angular/core'; +import { Component, Input, HostBinding, ElementRef, forwardRef, HostListener } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { FormModule } from '../../form-module'; @Component({ selector: 'ui-input', - imports: [], + exportAs: 'uiInput', + standalone: true, + imports: [CommonModule], templateUrl: './input.component.html', - styleUrl: './input.component.scss' + styleUrl: './input.component.scss', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputComponent), + multi: true + } + ] }) -export class InputComponent { +export class InputComponent implements ControlValueAccessor { + @Input() + placeholder: string = ''; + @Input() + type: string = 'text'; + + @Input() + @HostBinding('class.disabled') + disabled: boolean = false; + + @Input() + required: boolean = false; + + value: string = ''; + + @HostBinding('class.focused') + isFocused: boolean = false; + + @HostBinding('class.hovered') + isHovered: boolean = false; + + isTouched: boolean = false; + + @HostBinding('class.active') + isActive: boolean = false; + + private onChange = (value: string) => {}; + private onTouched = () => {}; + + @HostBinding('class.filled') + get filled() { + return this.value && this.value.length > 0; + } + + @HostBinding('class.invalid') + get invalid() { + return this.required && this.isTouched && (!this.value || this.value.length === 0); + } + + constructor(private elementRef: ElementRef) {} + + public stopPropagation(event: Event) { + event.stopPropagation(); + } + + public focus() { + console.log({elementRef: this.elementRef.nativeElement}); + this.elementRef.nativeElement.querySelector('input')?.focus(); + this.onInputFocus(); + } + + public blur() { + this.elementRef.nativeElement.querySelector('input')?.blur(); + this.onInputBlur(); + } + + @HostListener('mouseenter') + public hover() { + this.isHovered = true; + } + + @HostListener('mouseleave') + public leave() { + this.isHovered = false; + } + + onInputChange(event: Event): void { + const target = event.target as HTMLInputElement; + this.value = target.value; + this.onChange(this.value); + } + + onInputFocus(): void { + this.isFocused = true; + this.isActive = true; + FormModule.ActiveInput = this; + (window as any).KeyEventSafeModeEnabled = true; + } + + onInputBlur(): void { + this.isFocused = false; + this.isActive = false; + this.isTouched = true; + this.onTouched(); + FormModule.ActiveInput = undefined; + (window as any).KeyEventSafeModeEnabled = undefined; + } + + @HostListener('click') + onContainerClick(): void { + const input = this.elementRef.nativeElement.querySelector('input'); + if (input && !this.disabled) { + this.focus(); + } + } + + // ControlValueAccessor implementation + writeValue(value: string): void { + this.value = value || ''; + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } } diff --git a/src/lib/modules/form/form-module.ts b/src/lib/modules/form/form-module.ts index b8a8ef6..52015c7 100644 --- a/src/lib/modules/form/form-module.ts +++ b/src/lib/modules/form/form-module.ts @@ -1,12 +1,17 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; - - +import { InputComponent } from './components/input/input.component'; @NgModule({ declarations: [], imports: [ - CommonModule + CommonModule, + InputComponent + ], + exports: [ + InputComponent ] }) -export class FormModule { } +export class FormModule { + public static ActiveInput?: InputComponent; +} diff --git a/src/lib/styles/README-UI-CONTRAST-COLOR.md b/src/lib/styles/README-UI-CONTRAST-COLOR.md new file mode 100644 index 0000000..0291be7 --- /dev/null +++ b/src/lib/styles/README-UI-CONTRAST-COLOR.md @@ -0,0 +1,141 @@ +# UI Contrast Color Function + +## Opis + +Funkcja `ui-contrast-color()` automatycznie wybiera najlepszy kolor kontrastujący na podstawie jasności koloru bazowego. Jest to narzędzie do zapewnienia optymalnej czytelności tekstu na różnych tłach. + +## Składnia + +```scss +@function ui-contrast-color($base-color, $light-color, $dark-color) +``` + +### Parametry + +- `$base-color` - Kolor bazowy, dla którego sprawdzana jest jasność +- `$light-color` - Kolor zwracany dla ciemnych kolorów bazowych (zwykle kolor tekstu w jasnym motywie lub tła w ciemnym motywie) +- `$dark-color` - Kolor zwracany dla jasnych kolorów bazowych (zwykle kolor tła w jasnym motywie lub tekstu w ciemnym motywie) + +### Zwracana wartość + +Funkcja zwraca: +- `$dark-color` jeśli jasność `$base-color` > 50% (kolor bliżej białego) +- `$light-color` jeśli jasność `$base-color` ≤ 50% (kolor bliżej czarnego) + +## Mechanizm działania + +1. Funkcja oblicza jasność koloru bazowego używając `color.channel($base-color, "lightness", $space: hsl)` +2. Porównuje jasność z progiem 50% +3. Zwraca odpowiedni kolor kontrastujący + +## Przykłady użycia + +### Podstawowe użycie + +```scss +@use 'theme-mixins' as theme; + +.button { + $bg: #007bff; // Niebieski + background-color: $bg; + color: theme.ui-contrast-color($bg, #ffffff, #333333); + // Wynik: biały tekst, bo niebieski jest ciemny +} +``` + +### Kolory statusów + +```scss +.status-badge { + &.success { + $color: #28a745; // Zielony + background-color: $color; + color: theme.ui-contrast-color($color, white, black); + // Wynik: biały tekst + } + + &.warning { + $color: #ffc107; // Żółty + background-color: $color; + color: theme.ui-contrast-color($color, white, black); + // Wynik: czarny tekst (żółty jest jasny) + } +} +``` + +### Użycie z motywami + +```scss +// Dla jasnego motywu +.component.light-theme { + $bg: var(--ui-color-primary); + $primary: #2c3e50; // Wartość z motywu + + background-color: $primary; + color: theme.ui-contrast-color($primary, #ffffff, #333333); +} +``` + +## Integracja z systemem motywów + +Funkcja jest już zintegrowana z systemem motywów biblioteki UI i automatycznie generuje zmienne contrast: + +### Zmienne HEX +- `--ui-color-primary-contrast` +- `--ui-color-secondary-contrast` +- `--ui-color-success-contrast` +- `--ui-color-warning-contrast` +- `--ui-color-danger-contrast` +- `--ui-color-info-contrast` + +### Zmienne RGB +- `--ui-color-primary-contrast-rgb` +- `--ui-color-secondary-contrast-rgb` +- `--ui-color-success-contrast-rgb` +- `--ui-color-warning-contrast-rgb` +- `--ui-color-danger-contrast-rgb` +- `--ui-color-info-contrast-rgb` + +## Użycie zmiennych CSS + +```scss +.dynamic-component { + background-color: var(--ui-color-primary); + color: var(--ui-color-primary-contrast); + + // Z przezroczystością + background-color: rgba(var(--ui-color-primary-rgb), 0.8); + color: var(--ui-color-primary-contrast); + + // Obramowanie z przezroczystym kontrastem + border: 1px solid rgba(var(--ui-color-primary-contrast-rgb), 0.3); +} +``` + +## Zalety + +1. **Automatyczny dobór** - Nie musisz ręcznie sprawdzać, czy kolor jest jasny czy ciemny +2. **Spójność** - Wszystkie komponenty używają tej samej logiki kontrastowania +3. **Dostępność** - Zapewnia lepszą czytelność dla użytkowników +4. **Elastyczność** - Możesz definiować własne kolory jasne i ciemne dla różnych kontekstów +5. **Integracja z motywami** - Automatycznie generuje zmienne CSS dla wszystkich motywów + +## Uwagi techniczne + +- Funkcja działa w czasie kompilacji SCSS z konkretnymi wartościami kolorów +- Dla dynamicznych kolorów CSS używaj pre-wygenerowanych zmiennych `*-contrast` +- Próg 50% jasności jest optymalny dla większości przypadków użycia +- Używa nowoczesnej składni Sass (`color.channel()`) bez ostrzeżeń o deprecacji + +## Przykłady kolorów i wyników + +| Kolor bazowy | Nazwa | Jasność | Wynik dla (white, black) | +|--------------|-------|---------|--------------------------| +| `#ffffff` | Biały | 100% | `black` | +| `#ffc107` | Żółty | ~75% | `black` | +| `#28a745` | Zielony | ~45% | `white` | +| `#007bff` | Niebieski | ~35% | `white` | +| `#dc3545` | Czerwony | ~30% | `white` | +| `#000000` | Czarny | 0% | `white` | + +Funkcja zapewnia optymalny kontrast dla wszystkich kolorów w systemie motywów biblioteki UI. diff --git a/src/lib/styles/_theme-mixins.scss b/src/lib/styles/_theme-mixins.scss index aaa2ea9..21efa3e 100644 --- a/src/lib/styles/_theme-mixins.scss +++ b/src/lib/styles/_theme-mixins.scss @@ -1,12 +1,29 @@ /* Theme Integration Mixins for UI Library */ +@use 'sass:color'; + +/** + * Mixin that returns the best contrasting color based on lightness + * If the base color is closer to white than black, returns the dark color + * If the base color is closer to black than white, returns the light color + * + * @param $base-color - The color to check lightness for + * @param $light-color - Color to return for dark base colors (usually text color in light theme or background in dark theme) + * @param $dark-color - Color to return for light base colors (usually background color in light theme or text color in dark theme) + * @return Color - The contrasting color + */ +@function ui-contrast-color($base-color, $light-color, $dark-color) { + $lightness: color.channel($base-color, "lightness", $space: hsl); + @return if($lightness > 40%, $dark-color, $light-color); +} + /** * Mixin that generates CSS variables for UI library components * This mixin should be called by the main application to provide colors for the UI library * The UI library uses only --ui-* variables with hardcoded fallbacks for portability - * + * * @param $primary - Primary color for the theme - * @param $secondary - Secondary color for the theme + * @param $secondary - Secondary color for the theme * @param $background - Background color for the theme * @param $text - Text color for the theme * @param $border - Border color for the theme @@ -18,37 +35,155 @@ $background: #343a40, $text: #ffffff, $border: #d0d0d0, - $hover: rgba(255, 255, 255, 0.1) + $hover: rgba(255, 255, 255, 0.1), + $success: #28a745, + $warning: #ffc107, + $danger: #dc3545, + $info: #17a2b8 ) { - /* UI Library specific CSS variables - only these should be used in UI components */ - --ui-color-primary: #{$primary}; - --ui-color-secondary: #{$secondary}; - --ui-color-background: #{$background}; - --ui-color-text: #{$text}; - --ui-color-border: #{$border}; - --ui-color-hover: #{$hover}; - - /* Derived colors for specific UI components */ - --ui-color-menu-bg: #{$background}; - --ui-color-menu-text: #{$text}; - --ui-color-menu-hover: #{$hover}; - --ui-color-menu-active: color-mix(in srgb, #{$background} 80%, black 20%); - --ui-color-menu-border: #{$border}; - - --ui-color-content-bg: color-mix(in srgb, #{$background} 95%, white 5%); - --ui-color-content-text: #{$text}; - - --ui-color-icon-primary: #{$primary}; - --ui-color-icon-secondary: #{$secondary}; - --ui-color-icon-muted: color-mix(in srgb, #{$text} 60%, transparent 40%); - - /* Card component variables */ - --ui-color-card-bg: #{if($background == #343a40, #ffffff, color-mix(in srgb, #{$background} 95%, white 5%))}; - --ui-color-card-border: #{$border}; - --ui-color-card-title: #{$primary}; - --ui-color-card-text: #{if($text == #ffffff, #333333, $text)}; - --ui-color-card-footer-bg: #{if($background == #343a40, #f8f9fa, color-mix(in srgb, #{$background} 90%, white 10%))}; - + /* Assign SCSS variables for each color */ + $primary-color: $primary; + $secondary-color: $secondary; + $background-color: $background; + $text-color: $text; + $border-color: $border; + $hover-color: $hover; + $success-color: $success; + $warning-color: $warning; + $danger-color: $danger; + $info-color: $info; + + /* Derived colors as SCSS variables using SCSS functions */ + $menu-active-color: color.mix(black, $background-color, 20%); + $content-bg-color: color.mix(white, $background-color, 5%); + $icon-muted-color: rgba($text-color, 0.6); + $card-bg-color: if($background-color == #343a40, #ffffff, color.mix(white, $background-color, 5%)); + $card-text-color: if($text-color == #ffffff, #333333, $text-color); + $card-footer-bg-color: if($background-color == #343a40, #f8f9fa, color.mix(white, $background-color, 10%)); + + /* UI Library specific CSS variables - HEX format */ + --ui-color-primary: #{$primary-color}; + --ui-color-secondary: #{$secondary-color}; + --ui-color-background: #{$background-color}; + --ui-color-text: #{$text-color}; + --ui-color-border: #{$border-color}; + --ui-color-hover: #{$hover-color}; + + /* Derived colors for specific UI components - HEX format */ + --ui-color-menu-bg: #{$background-color}; + --ui-color-menu-text: #{$text-color}; + --ui-color-menu-hover: #{$hover-color}; + --ui-color-menu-active: #{$menu-active-color}; + --ui-color-menu-border: #{$border-color}; + + --ui-color-content-bg: #{$content-bg-color}; + --ui-color-content-text: #{$text-color}; + + --ui-color-icon-primary: #{$primary-color}; + --ui-color-icon-secondary: #{$secondary-color}; + --ui-color-icon-muted: #{$icon-muted-color}; + + /* Card component variables - HEX format */ + --ui-color-card-bg: #{$card-bg-color}; + --ui-color-card-border: #{$border-color}; + --ui-color-card-title: #{$primary-color}; + --ui-color-card-text: #{$card-text-color}; + --ui-color-card-footer-bg: #{$card-footer-bg-color}; + + /* Status colors - HEX format */ + --ui-color-success: #{$success-color}; + --ui-color-warning: #{$warning-color}; + --ui-color-danger: #{$danger-color}; + --ui-color-info: #{$info-color}; + + /* Contrast colors - automatically choose text or background color for best contrast */ + --ui-color-primary-contrast: #{ui-contrast-color($primary-color, $text-color, $background-color)}; + --ui-color-secondary-contrast: #{ui-contrast-color($secondary-color, $text-color, $background-color)}; + --ui-color-success-contrast: #{ui-contrast-color($success-color, $text-color, $background-color)}; + --ui-color-warning-contrast: #{ui-contrast-color($warning-color, $text-color, $background-color)}; + --ui-color-danger-contrast: #{ui-contrast-color($danger-color, $text-color, $background-color)}; + --ui-color-info-contrast: #{ui-contrast-color($info-color, $text-color, $background-color)}; + + /* Hard colors - darker in light themes, lighter in dark themes */ + --ui-color-primary-hard: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $primary-color, 20%), color.mix(white, $primary-color, 20%))}; + --ui-color-secondary-hard: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $secondary-color, 20%), color.mix(white, $secondary-color, 20%))}; + --ui-color-success-hard: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $success-color, 20%), color.mix(white, $success-color, 20%))}; + --ui-color-warning-hard: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $warning-color, 20%), color.mix(white, $warning-color, 20%))}; + --ui-color-danger-hard: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $danger-color, 20%), color.mix(white, $danger-color, 20%))}; + --ui-color-info-hard: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $info-color, 20%), color.mix(white, $info-color, 20%))}; + + /* Soft colors - lighter in light themes, darker in dark themes */ + --ui-color-primary-soft: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $primary-color, 20%), color.mix(black, $primary-color, 20%))}; + --ui-color-secondary-soft: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $secondary-color, 20%), color.mix(black, $secondary-color, 20%))}; + --ui-color-success-soft: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $success-color, 20%), color.mix(black, $success-color, 20%))}; + --ui-color-warning-soft: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $warning-color, 20%), color.mix(black, $warning-color, 20%))}; + --ui-color-danger-soft: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $danger-color, 20%), color.mix(black, $danger-color, 20%))}; + --ui-color-info-soft: #{if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $info-color, 20%), color.mix(black, $info-color, 20%))}; + + /* RGB versions for rgba() usage - Basic colors */ + --ui-color-primary-rgb: #{color.channel($primary-color, "red")}, #{color.channel($primary-color, "green")}, #{color.channel($primary-color, "blue")}; + --ui-color-secondary-rgb: #{color.channel($secondary-color, "red")}, #{color.channel($secondary-color, "green")}, #{color.channel($secondary-color, "blue")}; + --ui-color-background-rgb: #{color.channel($background-color, "red")}, #{color.channel($background-color, "green")}, #{color.channel($background-color, "blue")}; + --ui-color-text-rgb: #{color.channel($text-color, "red")}, #{color.channel($text-color, "green")}, #{color.channel($text-color, "blue")}; + --ui-color-border-rgb: #{color.channel($border-color, "red")}, #{color.channel($border-color, "green")}, #{color.channel($border-color, "blue")}; + + /* Menu colors RGB versions */ + --ui-color-menu-bg-rgb: #{color.channel($background-color, "red")}, #{color.channel($background-color, "green")}, #{color.channel($background-color, "blue")}; + --ui-color-menu-text-rgb: #{color.channel($text-color, "red")}, #{color.channel($text-color, "green")}, #{color.channel($text-color, "blue")}; + --ui-color-menu-border-rgb: #{color.channel($border-color, "red")}, #{color.channel($border-color, "green")}, #{color.channel($border-color, "blue")}; + + /* Content colors RGB versions */ + --ui-color-content-text-rgb: #{color.channel($text-color, "red")}, #{color.channel($text-color, "green")}, #{color.channel($text-color, "blue")}; + --ui-color-content-bg-rgb: #{color.channel($content-bg-color, "red")}, #{color.channel($content-bg-color, "green")}, #{color.channel($content-bg-color, "blue")}; + + /* Icon colors RGB versions */ + --ui-color-icon-primary-rgb: #{color.channel($primary-color, "red")}, #{color.channel($primary-color, "green")}, #{color.channel($primary-color, "blue")}; + --ui-color-icon-secondary-rgb: #{color.channel($secondary-color, "red")}, #{color.channel($secondary-color, "green")}, #{color.channel($secondary-color, "blue")}; + + /* Menu active RGB version */ + --ui-color-menu-active-rgb: #{color.channel($menu-active-color, "red")}, #{color.channel($menu-active-color, "green")}, #{color.channel($menu-active-color, "blue")}; + + /* Card colors RGB versions */ + --ui-color-card-border-rgb: #{color.channel($border-color, "red")}, #{color.channel($border-color, "green")}, #{color.channel($border-color, "blue")}; + --ui-color-card-title-rgb: #{color.channel($primary-color, "red")}, #{color.channel($primary-color, "green")}, #{color.channel($primary-color, "blue")}; + --ui-color-card-bg-rgb: #{color.channel($card-bg-color, "red")}, #{color.channel($card-bg-color, "green")}, #{color.channel($card-bg-color, "blue")}; + --ui-color-card-text-rgb: #{color.channel($card-text-color, "red")}, #{color.channel($card-text-color, "green")}, #{color.channel($card-text-color, "blue")}; + --ui-color-card-footer-bg-rgb: #{color.channel($card-footer-bg-color, "red")}, #{color.channel($card-footer-bg-color, "green")}, #{color.channel($card-footer-bg-color, "blue")}; + + /* Status colors RGB versions */ + --ui-color-success-rgb: #{color.channel($success-color, "red")}, #{color.channel($success-color, "green")}, #{color.channel($success-color, "blue")}; + --ui-color-warning-rgb: #{color.channel($warning-color, "red")}, #{color.channel($warning-color, "green")}, #{color.channel($warning-color, "blue")}; + --ui-color-danger-rgb: #{color.channel($danger-color, "red")}, #{color.channel($danger-color, "green")}, #{color.channel($danger-color, "blue")}; + --ui-color-info-rgb: #{color.channel($info-color, "red")}, #{color.channel($info-color, "green")}, #{color.channel($info-color, "blue")}; + + /* Contrast colors RGB versions */ + --ui-color-primary-contrast-rgb: #{color.channel(ui-contrast-color($primary-color, $text-color, $background-color), "red")}, #{color.channel(ui-contrast-color($primary-color, $text-color, $background-color), "green")}, #{color.channel(ui-contrast-color($primary-color, $text-color, $background-color), "blue")}; + --ui-color-secondary-contrast-rgb: #{color.channel(ui-contrast-color($secondary-color, $text-color, $background-color), "red")}, #{color.channel(ui-contrast-color($secondary-color, $text-color, $background-color), "green")}, #{color.channel(ui-contrast-color($secondary-color, $text-color, $background-color), "blue")}; + --ui-color-success-contrast-rgb: #{color.channel(ui-contrast-color($success-color, $text-color, $background-color), "red")}, #{color.channel(ui-contrast-color($success-color, $text-color, $background-color), "green")}, #{color.channel(ui-contrast-color($success-color, $text-color, $background-color), "blue")}; + --ui-color-warning-contrast-rgb: #{color.channel(ui-contrast-color($warning-color, $text-color, $background-color), "red")}, #{color.channel(ui-contrast-color($warning-color, $text-color, $background-color), "green")}, #{color.channel(ui-contrast-color($warning-color, $text-color, $background-color), "blue")}; + --ui-color-danger-contrast-rgb: #{color.channel(ui-contrast-color($danger-color, $text-color, $background-color), "red")}, #{color.channel(ui-contrast-color($danger-color, $text-color, $background-color), "green")}, #{color.channel(ui-contrast-color($danger-color, $text-color, $background-color), "blue")}; + --ui-color-info-contrast-rgb: #{color.channel(ui-contrast-color($info-color, $text-color, $background-color), "red")}, #{color.channel(ui-contrast-color($info-color, $text-color, $background-color), "green")}, #{color.channel(ui-contrast-color($info-color, $text-color, $background-color), "blue")}; + + /* Hard colors RGB versions */ + --ui-color-primary-hard-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $primary-color, 20%), color.mix(white, $primary-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $primary-color, 20%), color.mix(white, $primary-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $primary-color, 20%), color.mix(white, $primary-color, 20%)), "blue")}; + --ui-color-secondary-hard-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $secondary-color, 20%), color.mix(white, $secondary-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $secondary-color, 20%), color.mix(white, $secondary-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $secondary-color, 20%), color.mix(white, $secondary-color, 20%)), "blue")}; + --ui-color-success-hard-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $success-color, 20%), color.mix(white, $success-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $success-color, 20%), color.mix(white, $success-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $success-color, 20%), color.mix(white, $success-color, 20%)), "blue")}; + --ui-color-warning-hard-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $warning-color, 20%), color.mix(white, $warning-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $warning-color, 20%), color.mix(white, $warning-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $warning-color, 20%), color.mix(white, $warning-color, 20%)), "blue")}; + --ui-color-danger-hard-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $danger-color, 20%), color.mix(white, $danger-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $danger-color, 20%), color.mix(white, $danger-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $danger-color, 20%), color.mix(white, $danger-color, 20%)), "blue")}; + --ui-color-info-hard-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $info-color, 20%), color.mix(white, $info-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $info-color, 20%), color.mix(white, $info-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(black, $info-color, 20%), color.mix(white, $info-color, 20%)), "blue")}; + + /* Soft colors RGB versions */ + --ui-color-primary-soft-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $primary-color, 20%), color.mix(black, $primary-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $primary-color, 20%), color.mix(black, $primary-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $primary-color, 20%), color.mix(black, $primary-color, 20%)), "blue")}; + --ui-color-secondary-soft-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $secondary-color, 20%), color.mix(black, $secondary-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $secondary-color, 20%), color.mix(black, $secondary-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $secondary-color, 20%), color.mix(black, $secondary-color, 20%)), "blue")}; + --ui-color-success-soft-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $success-color, 20%), color.mix(black, $success-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $success-color, 20%), color.mix(black, $success-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $success-color, 20%), color.mix(black, $success-color, 20%)), "blue")}; + --ui-color-warning-soft-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $warning-color, 20%), color.mix(black, $warning-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $warning-color, 20%), color.mix(black, $warning-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $warning-color, 20%), color.mix(black, $warning-color, 20%)), "blue")}; + --ui-color-danger-soft-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $danger-color, 20%), color.mix(black, $danger-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $danger-color, 20%), color.mix(black, $danger-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $danger-color, 20%), color.mix(black, $danger-color, 20%)), "blue")}; + --ui-color-info-soft-rgb: #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $info-color, 20%), color.mix(black, $info-color, 20%)), "red")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $info-color, 20%), color.mix(black, $info-color, 20%)), "green")}, #{color.channel(if(color.channel($background-color, "lightness", $space: hsl) > 50%, color.mix(white, $info-color, 20%), color.mix(black, $info-color, 20%)), "blue")}; + + /* Note: hover and icon-muted use rgba(), so they already have alpha transparency built-in */ + /* For hover effects, use the HEX version: var(--ui-color-hover) */ + /* For icon-muted, use the HEX version: var(--ui-color-icon-muted) */ + /* Card spacing and layout variables */ --ui-spacing-xs: 4px; --ui-spacing-sm: 8px; @@ -58,10 +193,23 @@ --ui-font-size-lg: 18px; --ui-font-weight-semibold: 600; --ui-card-header-min-height: 40px; - + /* Card shadows */ --ui-shadow-card: 0 2px 4px rgba(0, 0, 0, 0.1); --ui-shadow-card-hover: 0 4px 8px rgba(0, 0, 0, 0.15); + + /* Input component variables */ + --ui-color-input-bg: #{color-mix(in srgb, #{$background} 95%, white 5%)}; + --ui-color-input-text: #{color-mix(in srgb, #{$text} 95%, white 5%)}; + --ui-color-input-placeholder: #{color-mix(in srgb, #{$primary} 50%, transparent 50%)}; + --ui-color-input-label-focus: #{color-mix(in srgb, #{$primary} 95%, white 5%)}; + --ui-color-input-label-invalid: #dc3545; + + /* Input box-shadow borders and effects */ + --ui-shadow-input-border: 0 0 0 0px #{$primary}, 0 0 0 0px #{if($background == #343a40, #ffffff, color-mix(in srgb, #{$background} 95%, white 5%))}, inset 0 0 0 1px #{$border}; + --ui-shadow-input-focus: 0 0 0 0px #{$primary}, 0 0 0 0px #{if($background == #343a40, #ffffff, color-mix(in srgb, #{$background} 95%, white 5%))}, inset 0 0 0 2px #{$primary}; + --ui-shadow-input-active: 0 0 0 10px #{$primary}, 0 0 0 9px #{if($background == #343a40, #ffffff, color-mix(in srgb, #{$background} 95%, white 5%))}; + --ui-shadow-input-invalid: 0 0 0 0px #{$primary}, 0 0 0 0px #{if($background == #343a40, #ffffff, color-mix(in srgb, #{$background} 95%, white 5%))}, inset 0 0 0 2px #dc3545; } /** @@ -71,11 +219,11 @@ background: var(--ui-color-menu-bg); color: var(--ui-color-menu-text); border-right: 1px solid var(--ui-color-menu-border); - + &.active { background: var(--ui-color-menu-active); } - + &.focus, &:active, &:hover { @@ -112,19 +260,19 @@ border: 1px solid var(--ui-color-card-border); border-radius: var(--ui-border-radius); box-shadow: var(--ui-shadow-card); - + &:hover { box-shadow: var(--ui-shadow-card-hover); } - + .ui-card-title { color: var(--ui-color-card-title); } - + .ui-card-content { color: var(--ui-color-card-text); } - + .ui-card-footer { background: var(--ui-color-card-footer-bg); border-top-color: var(--ui-color-card-border); diff --git a/src/lib/styles/ui-contrast-color-examples.scss b/src/lib/styles/ui-contrast-color-examples.scss new file mode 100644 index 0000000..8d52c86 --- /dev/null +++ b/src/lib/styles/ui-contrast-color-examples.scss @@ -0,0 +1,150 @@ +/* Examples of using ui-contrast-color function in UI Library */ + +@use 'sass:color'; +@use 'theme-mixins' as theme; + +/* Example 1: Basic usage with predefined colors */ +.example-button { + $button-bg: #007bff; // Blue background + $light-text: #ffffff; // White text for dark backgrounds + $dark-text: #333333; // Dark text for light backgrounds + + background-color: $button-bg; + color: theme.ui-contrast-color($button-bg, $light-text, $dark-text); + // Result: white text because blue is darker than 50% lightness +} + +/* Example 2: Using with theme variables */ +.example-card { + $card-bg: var(--ui-color-primary); + + // In SCSS context, you need to use actual color values + $primary-color: #2c3e50; // This would come from your theme + $text-light: #ffffff; + $text-dark: #333333; + + background-color: $primary-color; + color: theme.ui-contrast-color($primary-color, $text-light, $text-dark); + // Result: white text because #2c3e50 is dark +} + +/* Example 3: Status colors with automatic contrast */ +.status-examples { + .success-badge { + $success: #28a745; // Green + $light: #ffffff; + $dark: #000000; + + background-color: $success; + color: theme.ui-contrast-color($success, $light, $dark); + // Result: white text because green is relatively dark + } + + .warning-badge { + $warning: #ffc107; // Yellow + $light: #ffffff; + $dark: #000000; + + background-color: $warning; + color: theme.ui-contrast-color($warning, $light, $dark); + // Result: black text because yellow is light (>50% lightness) + } + + .danger-badge { + $danger: #dc3545; // Red + $light: #ffffff; + $dark: #000000; + + background-color: $danger; + color: theme.ui-contrast-color($danger, $light, $dark); + // Result: white text because red is dark + } + + .info-badge { + $info: #17a2b8; // Cyan + $light: #ffffff; + $dark: #000000; + + background-color: $info; + color: theme.ui-contrast-color($info, $light, $dark); + // Result: white text because cyan is dark + } +} + +/* Example 4: Using with different light/dark colors based on theme */ +.theme-aware-component { + // For light theme + $bg-light: #f8f9fa; + $text-light: #333333; + $bg-dark: #343a40; + $text-dark: #ffffff; + + // Component background + $component-bg: #007bff; + + // In light theme context + &.light-theme { + background-color: $component-bg; + color: theme.ui-contrast-color($component-bg, $text-dark, $text-light); + // For light theme: if bg is light, use dark text; if bg is dark, use light text + } + + // In dark theme context + &.dark-theme { + background-color: $component-bg; + color: theme.ui-contrast-color($component-bg, $text-dark, $bg-light); + // For dark theme: if bg is light, use light bg color; if bg is dark, use dark text + } +} + +/* Example 5: Dynamic contrast with CSS variables (conceptual) */ +/* +Note: The ui-contrast-color function works at compile time with SCSS variables. +For runtime CSS variables, you would use the pre-computed contrast variables: + +.dynamic-component { + background-color: var(--ui-color-primary); + color: var(--ui-color-primary-contrast); + + // Or with RGB for transparency: + background-color: rgba(var(--ui-color-primary-rgb), 0.8); + color: var(--ui-color-primary-contrast); +} +*/ + +/* Example 6: Complex usage with mixed colors */ +.complex-example { + $base-colors: ( + 'primary': #2c3e50, + 'secondary': #6c757d, + 'light': #f8f9fa, + 'dark': #343a40 + ); + + $text-light: #ffffff; + $text-dark: #333333; + + @each $name, $color in $base-colors { + .#{$name}-variant { + background-color: $color; + color: theme.ui-contrast-color($color, $text-light, $text-dark); + + // Border with semi-transparent contrast color + $contrast: theme.ui-contrast-color($color, $text-light, $text-dark); + border: 2px solid rgba($contrast, 0.3); + } + } +} + +/* How the function works: + * + * ui-contrast-color($base-color, $light-color, $dark-color) + * + * 1. Calculates lightness of $base-color using HSL color space + * 2. If lightness > 50% (closer to white): returns $dark-color + * 3. If lightness ≤ 50% (closer to black): returns $light-color + * + * This ensures optimal contrast for readability: + * - Light backgrounds get dark text + * - Dark backgrounds get light text + */