resources/sass/components/base/_variables.scss
// ─── CzG UI Registry — base/variables ───────────────────────────────────────
// Konvence:
// - Canonical názvy: `$cgui-*` (oficiální API)
// - Legacy aliases: `$color__*`, `$small`, `$radius`... (zachováno pro kompatibilitu
// se stávajícími komponentami; nové komponenty MAJÍ POUŽÍVAT `$cgui-*`)
// - Project override: nastav `$cgui-color-primary: #FF0000;` PŘED `@import`
// - Px only (žádné rem/em — user může mít zoom 200% a layout se rozjede)
// ─── 0. Theme switch flag (registry preview compile) ────────────────────────
$cgui-dark-mode: false !default;
// ─── 1. Brand colors ────────────────────────────────────────────────────────
@if $cgui-dark-mode {
$cgui-color-primary: #5577FF !default;
$cgui-color-secondary: #94A3B8 !default;
$cgui-color-brand: #5577FF !default;
$cgui-color-commerce: #34D399 !default;
$cgui-color-discount: #F87171 !default;
$cgui-color-yellow: #FDE047 !default;
$cgui-color-red: #F87171 !default;
$cgui-color-green: #34D399 !default;
$cgui-color-blue: #5577FF !default;
$cgui-color-access: #F1F5F9 !default;
$cgui-color-text: #F1F5F9 !default;
$cgui-color-text-muted: #94A3B8 !default;
$cgui-color-text-gray: #94A3B8 !default;
$cgui-color-text-dark: #FFFFFF !default;
$cgui-color-text-light: #CBD5E1 !default;
$cgui-color-bg: #0F172A !default;
$cgui-color-bg-light: #1E293B !default;
$cgui-color-bg-gray: #334155 !default;
$cgui-color-bg-gray-light: #1E293B !default;
$cgui-color-bg-gray-dark: #475569 !default;
$cgui-color-bg-form: #1E293B !default;
$cgui-color-bg-selected: #14532D !default;
$cgui-color-bg-hover: #1E293B !default;
$cgui-color-bg-dark: #020617 !default;
$cgui-color-border: #334155 !default;
$cgui-color-border-light: #1E293B !default;
$cgui-color-error: #F87171 !default;
$cgui-color-success: #34D399 !default;
$cgui-color-warning: #FBBF24 !default;
} @else {
$cgui-color-primary: #003CFF !default;
$cgui-color-secondary: #000000 !default;
$cgui-color-brand: #003CFF !default;
$cgui-color-commerce: #00880B !default;
$cgui-color-discount: #FF0057 !default;
$cgui-color-yellow: #FFEE00 !default;
$cgui-color-red: #FF5656 !default;
$cgui-color-green: #18B21D !default;
$cgui-color-blue: #003CFF !default;
$cgui-color-access: #000000 !default;
$cgui-color-text: #1F2937 !default;
$cgui-color-text-muted: #6B7280 !default;
$cgui-color-text-gray: #848484 !default;
$cgui-color-text-dark: #000000 !default;
$cgui-color-text-light: #6B7280 !default;
$cgui-color-bg: #FFFFFF !default;
$cgui-color-bg-light: #FFFFFF !default;
$cgui-color-bg-gray: #CCCCCC !default;
$cgui-color-bg-gray-light: #F1F1F1 !default;
$cgui-color-bg-gray-dark: #DFDFDF !default;
$cgui-color-bg-form: #F3F3F3 !default;
$cgui-color-bg-selected: #CFFFE4 !default;
$cgui-color-bg-hover: #F3F4F6 !default;
$cgui-color-bg-dark: #000000 !default;
$cgui-color-border: #DFDFDF !default;
$cgui-color-border-light: #F3F3F3 !default;
$cgui-color-error: #EF4444 !default;
$cgui-color-success: #00880B !default;
$cgui-color-warning: #F59E0B !default;
}
// ─── 2. Spacing scale (multiples of base — px only) ─────────────────────────
$cgui-space-base: 8px !default;
$cgui-space-0: 0 !default;
$cgui-space-1: 4px !default;
$cgui-space-2: 8px !default;
$cgui-space-3: 12px !default;
$cgui-space-4: 16px !default;
$cgui-space-5: 24px !default;
$cgui-space-6: 32px !default;
$cgui-space-7: 48px !default;
$cgui-space-8: 64px !default;
$cgui-space-9: 96px !default;
// ─── 3. Typography (px — žádné rem/em pro nezávislost na zoomu) ─────────────
$cgui-font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !default;
$cgui-font-family-heading: $cgui-font-family-base !default;
$cgui-font-family-mono: "SF Mono", Menlo, Consolas, monospace !default;
$cgui-font-size-xs: 12px !default;
$cgui-font-size-sm: 14px !default;
$cgui-font-size-md: 16px !default;
$cgui-font-size-lg: 18px !default;
$cgui-font-size-xl: 24px !default;
$cgui-font-size-xxl: 32px !default;
$cgui-font-weight-regular: 400 !default;
$cgui-font-weight-medium: 500 !default;
$cgui-font-weight-bold: 700 !default;
$cgui-line-height-tight: 1.2 !default;
$cgui-line-height-small: 1.35 !default;
$cgui-line-height-normal: 1.5 !default;
$cgui-line-height-loose: 1.75 !default;
// ─── 4. Layout ──────────────────────────────────────────────────────────────
$cgui-content: 1400px !default;
$cgui-content-medium: 970px !default;
$cgui-content-small: 768px !default;
$cgui-content-padding: 16px !default;
// ─── 5. Borders + radius ────────────────────────────────────────────────────
$cgui-radius-sm: 3px !default;
$cgui-radius: 6px !default;
$cgui-radius-lg: 12px !default;
$cgui-radius-pill: 999px !default;
$cgui-border-width: 1px !default;
// ─── 6. Shadows ─────────────────────────────────────────────────────────────
@if $cgui-dark-mode {
$cgui-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4) !default;
$cgui-shadow-main: 0 4px 12px rgba(0, 0, 0, 0.5) !default;
$cgui-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6) !default;
} @else {
$cgui-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06) !default;
$cgui-shadow-main: 0 4px 12px rgba(0, 0, 0, 0.08) !default;
$cgui-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12) !default;
}
// ─── 7. Transitions / easing ────────────────────────────────────────────────
$cgui-ease: all 0.15s ease !default;
$cgui-ease-in: all 0.2s ease-in !default;
$cgui-ease-out: all 0.2s ease-out !default;
$cgui-ease-in-out: all 0.25s ease-in-out !default;
// ─── 8. Breakpoints ─────────────────────────────────────────────────────────
$cgui-bp-min: 320px !default;
$cgui-bp-tiny: 480px !default;
$cgui-bp-small: 640px !default;
$cgui-bp-medium: 960px !default;
$cgui-bp-large: 1280px !default;
$cgui-bp-xlarge: 1700px !default;
$cgui-bp-max: 1920px !default;
// ─── 9. Z-index layers ──────────────────────────────────────────────────────
$cgui-z-base: 1 !default;
$cgui-z-dropdown: 100 !default;
$cgui-z-sticky: 500 !default;
$cgui-z-overlay: 900 !default;
$cgui-z-modal: 1000 !default;
$cgui-z-toast: 2000 !default;
$cgui-z-tooltip: 3000 !default;
// (žádné legacy aliasy — komponenty používají VÝHRADNĚ `$cgui-*` proměnné výše)
resources/sass/components/base/_functions.scss
// ─── Strip unit from value ──────────────────────────────────────────────────
// Use: cgui-strip-unit(16px) -> 16
@function cgui-strip-unit($value) {
@if type-of($value) == 'number' and not unitless($value) {
@return $value / ($value * 0 + 1);
}
@return $value;
}
// ─── Spacing helper ─────────────────────────────────────────────────────────
// Use: spacing(2) -> 16px (= 2 × $cgui-space-base)
@function cgui-spacing($n) {
@return $cgui-space-base * $n;
}
// ─── Semantic z-index lookup ────────────────────────────────────────────────
// Use: z(modal) -> 1000
@function cgui-z($layer) {
$layers: (
base: $cgui-z-base,
dropdown: $cgui-z-dropdown,
sticky: $cgui-z-sticky,
overlay: $cgui-z-overlay,
modal: $cgui-z-modal,
toast: $cgui-z-toast,
tooltip: $cgui-z-tooltip,
);
@if not map-has-key($layers, $layer) {
@warn "z(#{$layer}) — unknown layer. Available: #{map-keys($layers)}";
@return null;
}
@return map-get($layers, $layer);
}
// ─── Color shade helpers (build-time SCSS, hex/rgb only) ────────────────────
// Use: shade($cgui-color-primary, 10%) — ztmaví o 10%
// tint($cgui-color-primary, 10%) — světlejší o 10%
@function cgui-shade($color, $amount) {
@return mix(black, $color, $amount);
}
@function cgui-tint($color, $amount) {
@return mix(white, $color, $amount);
}
// ─── Encode color for SVG data-URI ──────────────────────────────────────────
// Use: background: url("data:...,#{encode-color($cgui-color-primary)}");
@function cgui-encode-color($color) {
@return '%23' + str-slice(inspect($color), 2);
}
resources/sass/components/base/_mixins.scss
// ─── Responsive ─────────────────────────────────────────────────────────────
// Use: @include media(small) { ... } nebo @include media(900px, down) { ... }
@mixin cgui-media($breakpoint, $direction: up) {
$bp: $breakpoint;
@if $breakpoint == tiny { $bp: $cgui-bp-tiny; }
@else if $breakpoint == small { $bp: $cgui-bp-small; }
@else if $breakpoint == medium { $bp: $cgui-bp-medium; }
@else if $breakpoint == large { $bp: $cgui-bp-large; }
@else if $breakpoint == xlarge { $bp: $cgui-bp-xlarge; }
@else if $breakpoint == max { $bp: $cgui-bp-max; }
@if $direction == up {
@media (min-width: $bp) { @content; }
} @else if $direction == down {
@media (max-width: ($bp - 1px)) { @content; }
} @else if $direction == only {
@media (min-width: $bp) and (max-width: ($bp + 320px - 1px)) { @content; }
}
}
// ─── Hover (skip touch) ─────────────────────────────────────────────────────
// Use: @include hover() { background: red; }
@mixin cgui-hover() {
@media (hover: hover) {
&:hover, &:focus-visible { @content; }
}
}
// ─── Focus visible (a11y) ───────────────────────────────────────────────────
// Use: a { @include focus-visible(); }
@mixin cgui-focus-visible($color: $cgui-color-primary, $offset: 2px) {
&:focus { outline: none; }
&:focus-visible {
outline: 2px solid $color;
outline-offset: $offset;
}
}
// ─── Visually hidden (sr-only) ──────────────────────────────────────────────
// Skryje vizuálně, ale zůstává pro screen readers.
@mixin cgui-visually-hidden() {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
// ─── Truncate text ──────────────────────────────────────────────────────────
// Use: @include truncate(); — single line
// @include truncate(3); — 3 řádky
@mixin cgui-truncate($lines: 1) {
@if $lines == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
// ─── Pseudo element shortcut ────────────────────────────────────────────────
// Use: &::before { @include pseudo(); ... }
@mixin cgui-pseudo($display: block, $position: absolute, $cgui-content: '') {
content: $cgui-content;
display: $display;
position: $position;
}
// ─── Container with max-width ───────────────────────────────────────────────
@mixin cgui-container($cgui-bp-max-width: $cgui-content, $padding: $cgui-content-padding) {
width: 100%;
max-width: $cgui-bp-max-width;
margin-left: auto;
margin-right: auto;
padding-left: $padding;
padding-right: $padding;
box-sizing: border-box;
}
// ─── Reset list (no bullets, no padding) ────────────────────────────────────
@mixin cgui-reset-list() {
list-style: none;
margin: 0;
padding: 0;
}
// ─── Reset button (strip native styles) ─────────────────────────────────────
@mixin cgui-reset-button() {
background: none;
border: none;
padding: 0;
margin: 0;
font: inherit;
color: inherit;
cursor: pointer;
text-align: inherit;
}
// ─── Aspect ratio (fallback for old browsers) ───────────────────────────────
// Modern: aspect-ratio property je dnes baseline
@mixin cgui-aspect-ratio($w, $h) {
aspect-ratio: $w / $h;
}
// ─── Scrollbar styling ──────────────────────────────────────────────────────
@mixin cgui-scrollbar-thin($thumb: $cgui-color-border, $track: transparent) {
scrollbar-width: thin;
scrollbar-color: $thumb $track;
&::-webkit-scrollbar { width: 8px; height: 8px; }
&::-webkit-scrollbar-track { background: $track; }
&::-webkit-scrollbar-thumb {
background: $thumb;
border-radius: 4px;
&:hover { background: $cgui-color-border-light; }
}
}
// ─── Stretched link (whole card clickable) ──────────────────────────────────
// Pattern: parent { position: relative; } a { @include stretched-link(); }
@mixin cgui-stretched-link() {
&::after {
content: '';
position: absolute;
inset: 0;
z-index: 1;
}
}
// ─── Center absolute ────────────────────────────────────────────────────────
@mixin cgui-center-absolute($axis: both) {
position: absolute;
@if $axis == both {
top: 50%; left: 50%;
transform: translate(-50%, -50%);
} @else if $axis == x {
left: 50%;
transform: translateX(-50%);
} @else if $axis == y {
top: 50%;
transform: translateY(-50%);
}
}
// ─── Card surface (běžné UI kontejnery) ─────────────────────────────────────
@mixin cgui-surface($padding: $cgui-space-4, $cgui-radius-arg: $cgui-radius, $shadow: $cgui-shadow-sm) {
background: $cgui-color-bg;
border: $cgui-border-width solid $cgui-color-border;
border-radius: $cgui-radius-arg;
padding: $padding;
box-shadow: $shadow;
}
resources/sass/components/base/_animations.scss
// Sdílené keyframes pro CzG UI komponenty.
@keyframes czg-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes czg-fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes czg-slide-down {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes czg-slide-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes czg-slide-in-right {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
resources/js/components/base/utils.js
// Sdílené JS utility pro CzG UI komponenty.
export function $(selector, root = document) {
return root.querySelector(selector);
}
export function $$(selector, root = document) {
return Array.from(root.querySelectorAll(selector));
}
export function on(el, event, handler, opts) {
el.addEventListener(event, handler, opts);
return () => el.removeEventListener(event, handler, opts);
}
export function debounce(fn, wait = 100) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
}
export function clamp(n, min, max) {
return Math.max(min, Math.min(max, n));
}
resources/js/components/base/outside-click.js
// Helper pro detekci kliku mimo element. Používají overlay komponenty (dropdown, popover, ...).
const handlers = new Map();
/**
* Zaregistruje callback který se zavolá při kliku mimo daný element.
* Vrací unsubscribe funkci.
*
* @param {HTMLElement} el — element, jehož "vnitřek" se ignoruje
* @param {Function} cb — callback při outside click
* @returns {Function} — unsubscribe
*/
export function onOutsideClick(el, cb) {
const handler = (e) => {
if (!el.contains(e.target)) {
cb(e);
}
};
// Registrace s mírným delayem, aby se nevyvolal click který komponentu otevřel.
requestAnimationFrame(() => document.addEventListener('click', handler));
const unsub = () => document.removeEventListener('click', handler);
handlers.set(el, unsub);
return unsub;
}
export function offOutsideClick(el) {
const unsub = handlers.get(el);
if (unsub) {
unsub();
handlers.delete(el);
}
}