Compare commits

...

2 Commits

Author SHA1 Message Date
Michał Sieciechowicz c1ed021196 input/button 2025-08-05 22:11:30 +02:00
Michał Sieciechowicz efb94f14da add card 2025-08-03 11:14:26 +02:00
22 changed files with 1260 additions and 64 deletions

View File

@ -264,6 +264,75 @@ export class AppComponent {
}
```
### Card Components
**Selectors:** `ui-card`, `ui-card-title`, `ui-card-menu`, `ui-card-footer`
**Description:** Zestaw komponentów do tworzenia kart z opcjonalnym nagłówkiem, menu i stopką.
**CardComponent (`ui-card`) Properties:**
- `title?: string` - Tytuł karty wyświetlany w nagłówku
- `margin: 0.5rem` - Domyślny margin karty
- Kolory jak ui-menu (--ui-color-menu-bg, --ui-color-menu-text)
**Standalone Components:**
- `ui-card-title` - Tytuł karty z własnymi stylami
- `ui-card-menu` - Menu/przyciski w nagłówku
- `ui-card-footer` - Stopka karty z własnymi stylami
**Usage:**
```html
<!-- Podstawowe użycie -->
<ui-card title="Tytuł karty">
<p>Zawartość karty</p>
</ui-card>
<!-- Pełne użycie z wszystkimi slotami -->
<ui-card title="Statystyki">
<ui-card-menu>
<button class="btn btn-sm">Edytuj</button>
<button class="btn btn-sm">Usuń</button>
</ui-card-menu>
<div class="stats">
<div class="stat-item">
<span class="value">123</span>
<span class="label">Użytkownicy</span>
</div>
</div>
<ui-card-footer>
<small class="text-muted">Ostatnia aktualizacja: dziś</small>
</ui-card-footer>
</ui-card>
<!-- Niestandardowy tytuł -->
<ui-card>
<ui-card-title>
<div class="d-flex align-items-center">
<i class="icon-chart"></i>
<span>Raporty sprzedaży</span>
</div>
</ui-card-title>
<ui-card-menu>
<span class="text-success">+12%</span>
</ui-card-menu>
<div class="chart-container">
<!-- wykres -->
</div>
</ui-card>
```
**CSS Variables:**
- `--ui-color-card-bg` - tło karty
- `--ui-color-card-border` - kolor obramowania
- `--ui-color-card-title` - kolor tytułu
- `--ui-color-card-text` - kolor tekstu
- `--ui-color-card-footer-bg` - tło stopki
- `--ui-shadow-card` - standardowy cień karty
- `--ui-shadow-card-hover` - cień przy hover
## Development
### Building

View File

@ -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;
}

View File

@ -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';
}

View File

@ -0,0 +1,186 @@
# Card Components
Zestaw komponentów do tworzenia kart z opcjonalnym nagłówkiem, menu i stopką.
## Komponenty
- `ui-card` - główny kontener karty
- `ui-card-title` - tytuł karty (standalone komponent)
- `ui-card-menu` - menu/przyciski w nagłówku (standalone komponent)
- `ui-card-footer` - stopka karty (standalone komponent)
## Użycie
### Podstawowe użycie z tytułem jako Input
```html
<ui-card title="Tytuł karty">
<p>Zawartość karty</p>
</ui-card>
```
### Użycie z komponentem ui-card-title
```html
<ui-card>
<ui-card-title>
<h2>Niestandardowy tytuł</h2>
<span class="badge">Nowy</span>
</ui-card-title>
<p>Zawartość karty</p>
</ui-card>
```
### Pełne użycie z wszystkimi komponentami
```html
<ui-card title="Statystyki">
<ui-card-menu>
<button class="btn btn-sm">Edytuj</button>
<button class="btn btn-sm">Usuń</button>
</ui-card-menu>
<div class="stats">
<div class="stat-item">
<span class="value">123</span>
<span class="label">Użytkownicy</span>
</div>
<div class="stat-item">
<span class="value">456</span>
<span class="label">Zamówienia</span>
</div>
</div>
<ui-card-footer>
<small class="text-muted">Ostatnia aktualizacja: dziś</small>
</ui-card-footer>
</ui-card>
```
### Użycie z komponentami ui-card-title i ui-card-menu
```html
<ui-card>
<ui-card-title>
<div class="d-flex align-items-center">
<i class="icon-chart"></i>
<span>Raporty sprzedaży</span>
</div>
</ui-card-title>
<ui-card-menu>
<div class="stats-mini">
<span class="text-success">+12%</span>
</div>
<button class="btn btn-outline-primary btn-sm">Szczegóły</button>
</ui-card-menu>
<div class="chart-container">
<!-- wykres -->
</div>
<ui-card-footer>
<div class="d-flex justify-content-between">
<span>Okres: ostatnie 30 dni</span>
<a href="#" class="text-primary">Zobacz więcej</a>
</div>
</ui-card-footer>
</ui-card>
```
## API
### CardComponent (`ui-card`)
**Inputs:**
| Właściwość | Typ | Domyślna | Opis |
|------------|-----|----------|------|
| `title` | `string` | `undefined` | Tytuł karty wyświetlany w nagłówku |
**Content Projection:**
| Selektor | Opis |
|----------|------|
| `ui-card-title` | Komponent tytułu karty |
| `ui-card-menu` | Komponent menu w nagłówku |
| (default) | Główna zawartość karty |
| `ui-card-footer` | Komponent stopki karty |
### CardTitleComponent (`ui-card-title`)
**Opis:** Standalone komponent do wyświetlania tytułu karty z własnymi stylami.
### CardMenuComponent (`ui-card-menu`)
**Opis:** Standalone komponent do wyświetlania menu/przycisków w nagłówku karty.
### CardFooterComponent (`ui-card-footer`)
**Opis:** Standalone komponent do wyświetlania stopki karty z własnymi stylami.
## Zmienne CSS
Komponent używa następujących zmiennych CSS z systemu motywów:
### Kolory
- `--ui-color-card-bg` - tło karty
- `--ui-color-card-border` - kolor obramowania
- `--ui-color-card-title` - kolor tytułu
- `--ui-color-card-text` - kolor tekstu
- `--ui-color-card-footer-bg` - tło stopki
### Spacing
- `--ui-spacing-xs` (4px) - małe odstępy
- `--ui-spacing-sm` (8px) - średnie odstępy
- `--ui-spacing-md` (16px) - standardowe padding
- `--ui-spacing-lg` (24px) - duże odstępy
### Layout
- `--ui-border-radius` (8px) - zaokrąglenie rogów
- `--ui-font-size-lg` (18px) - rozmiar czcionki tytułu
- `--ui-font-weight-semibold` (600) - grubość czcionki tytułu
- `--ui-card-header-min-height` (40px) - minimalna wysokość nagłówka
### Cienie
- `--ui-shadow-card` - standardowy cień karty
- `--ui-shadow-card-hover` - cień przy hover
## Responsywność
Na urządzeniach mobilnych (max-width: 768px):
- Nagłówek karty zmienia się na układ kolumnowy
- Menu wyrównuje się do prawej strony
- Zachowane są wszystkie funkcjonalności
## Przykłady stylizacji
### Karta z kolorowym nagłówkiem
```scss
.custom-card {
.ui-card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
margin: -16px -16px 0;
padding: 16px;
}
.ui-card-title {
color: white;
}
}
```
### Karta z ikoną
```html
<ui-card>
<ui-card-title>
<div class="d-flex align-items-center gap-2">
<i class="icon-settings"></i>
<span>Ustawienia</span>
</div>
</ui-card-title>
<!-- zawartość -->
</ui-card>
```

View File

@ -0,0 +1,7 @@
:host {
padding: 0.5rem;
display: flex;
&:empty{
display: none;
}
}

View File

@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'ui-card-footer',
standalone: true,
imports: [],
template: '<ng-content />',
styleUrl: './card-footer.component.scss'
})
export class CardFooterComponent {
}

View File

@ -0,0 +1,7 @@
:host {
display: flex;
gap: var(--ui-spacing-sm, 8px);
align-items: flex-start;
flex-shrink: 0;
padding: 1rem;
}

View File

@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'ui-card-menu',
standalone: true,
imports: [],
template: '<ng-content />',
styleUrl: './card-menu.component.scss'
})
export class CardMenuComponent {
}

View File

@ -0,0 +1,9 @@
:host {
display: block;
margin: 0;
font-size: var(--ui-font-size-lg, 18px);
font-weight: var(--ui-font-weight-semibold, 600);
color: var(--ui-color-menu-text, #ffffff);
line-height: 1.2;
padding: 1rem;
}

View File

@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'ui-card-title',
standalone: true,
imports: [],
template: `<ng-content />`,
styleUrl: './card-title.component.scss'
})
export class CardTitleComponent {
}

View File

@ -1 +1,15 @@
<p>card works!</p>
<div class="header">
@if (title) {
<div class="title">{{ title }}</div>
} @else {
<ng-content select="ui-card-title"></ng-content>
}
<ng-content select="ui-card-menu"></ng-content>
</div>
<div class="content">
<ng-content></ng-content>
</div>
<ng-content select="ui-card-footer"></ng-content>

View File

@ -0,0 +1,23 @@
:host {
display: block;
margin: var(--ui-spacing-sm, 0.5rem);
background: var(--ui-color-menu-bg, #343a40);
border: 1px solid var(--ui-color-menu-border, #d0d0d0);
border-radius: var(--ui-border-radius, 8px);
box-shadow: var(--ui-shadow-card, 0 2px 4px rgba(0, 0, 0, 0.1));
overflow: hidden;
transition: box-shadow 0.2s ease;
}
.content {
padding: var(--ui-spacing-md, 16px);
color: var(--ui-color-menu-text, #ffffff);
line-height: 1.5;
}
.header{
display: flex;
.title,
::ng-deep ui-card-title{
flex-grow: 1;
}
}

View File

@ -1,11 +1,13 @@
import { Component } from '@angular/core';
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'ui-card',
imports: [],
standalone: true,
imports: [CommonModule],
templateUrl: './card.component.html',
styleUrl: './card.component.scss'
})
export class CardComponent {
@Input() title?: string;
}

View File

@ -1 +1,18 @@
<p>input works!</p>
<div (click)="stopPropagation($event)">
<ng-content select="[place=before]"></ng-content>
</div>
<input
#input
[type]="type"
[value]="value"
[disabled]="disabled"
[required]="required"
(input)="onInputChange($event)"
(focus)="onInputFocus()"
(blur)="onInputBlur()"
class="ui-input-field"
/>
<label class="ui-input-label" [class.floating]="isFocused || filled">{{ placeholder }}</label>
<div (click)="stopPropagation($event)">
<ng-content select="[place=after]"></ng-content>
</div>

View File

@ -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;
}
}

View File

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputComponent } from './input.component';
describe('InputComponent', () => {
let component: InputComponent;
let fixture: ComponentFixture<InputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [InputComponent]
})
.compileComponents();
fixture = TestBed.createComponent(InputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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.

View File

@ -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,29 +35,181 @@
$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%);
/* 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;
--ui-spacing-md: 16px;
--ui-spacing-lg: 24px;
--ui-border-radius: 8px;
--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;
}
/**
@ -50,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 {
@ -83,4 +252,31 @@
}
}
/**
* Mixin that applies theme-aware styles to card components
*/
@mixin ui-card-theme() {
background: var(--ui-color-card-bg);
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);
}
}

View File

@ -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
*/

View File

@ -17,3 +17,6 @@ export * from './lib/modules/form/components/input/input.component';
// Components
export * from './lib/components/button/button.component';
export * from './lib/components/card/card.component';
export * from './lib/components/card/card-title/card-title.component';
export * from './lib/components/card/card-menu/card-menu.component';
export * from './lib/components/card/card-footer/card-footer.component';