Compare commits
2 Commits
678ce12de4
...
c1ed021196
Author | SHA1 | Date |
---|---|---|
|
c1ed021196 | |
|
efb94f14da |
69
README.md
69
README.md
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
```
|
|
@ -0,0 +1,7 @@
|
|||
:host {
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
&:empty{
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
:host {
|
||||
display: flex;
|
||||
gap: var(--ui-spacing-sm, 8px);
|
||||
align-items: flex-start;
|
||||
flex-shrink: 0;
|
||||
padding: 1rem;
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue