@eduboxpro/studio 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, signal, effect, Injectable, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, input, computed, ChangeDetectionStrategy, Component, output } from '@angular/core';
2
+ import { InjectionToken, inject, signal, effect, Injectable, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, input, computed, ChangeDetectionStrategy, Component, output, model } from '@angular/core';
3
3
  import { DOCUMENT } from '@angular/common';
4
4
  import * as i1 from 'lucide-angular';
5
- import { icons, LucideAngularModule, LucideIconProvider, LUCIDE_ICONS, Loader2, Loader, RotateCw, RefreshCw, Printer, Save, Image, Folder, FileText, File, HelpCircle, XCircle, CheckCircle, Info, AlertTriangle, AlertCircle, LogOut, LogIn, Unlock, Lock, EyeOff, Eye, Clock, Calendar, Bell, MoreHorizontal, MoreVertical, Menu, Home, Share2, Copy, Link, ExternalLink, Filter, Search, X, Check, Minus, Plus, Edit, Trash2, User, Settings, Star, Heart, Phone, Mail, Upload, Download, ChevronRight, ChevronLeft, ChevronUp, ChevronDown, ArrowLeft, ArrowRight } from 'lucide-angular';
5
+ import { icons, LucideAngularModule, LucideIconProvider, LUCIDE_ICONS, Moon, Sun, ShoppingCart, Loader2, Loader, RotateCw, RefreshCw, Printer, Save, Image, Folder, FileText, File, HelpCircle, XCircle, CheckCircle, Info, AlertTriangle, AlertCircle, LogOut, LogIn, Unlock, Lock, EyeOff, Eye, Clock, Calendar, Bell, MoreHorizontal, MoreVertical, Menu, Home, Share2, Copy, Link, ExternalLink, Filter, Search, X, Check, Minus, Plus, Edit, Trash2, User, Settings, Star, Heart, Phone, Mail, Upload, Download, ChevronRight, ChevronLeft, ChevronUp, ChevronDown, ArrowLeft, ArrowRight } from 'lucide-angular';
6
6
 
7
7
  /**
8
8
  * Injection token for Studio configuration
@@ -19,7 +19,7 @@ class StudioConfigService {
19
19
  constructor() {
20
20
  effect(() => {
21
21
  this.applyTheme(this.config().theme);
22
- }, { allowSignalWrites: false });
22
+ });
23
23
  }
24
24
  configure(config) {
25
25
  this.config.set(config);
@@ -35,29 +35,23 @@ class StudioConfigService {
35
35
  const newMode = this.themeMode() === 'light' ? 'dark' : 'light';
36
36
  this.setThemeMode(newMode);
37
37
  }
38
- async loadFromBackend(organizationSlug) {
39
- try {
40
- const response = await fetch(`/api/organizations/${organizationSlug}/design-config`);
41
- if (!response.ok) {
42
- throw new Error(`HTTP error! status: ${response.status}`);
43
- }
44
- const config = await response.json();
45
- this.configure(config);
46
- }
47
- catch (error) {
48
- console.error('[Studio] Failed to load config from backend:', error);
49
- throw error;
50
- }
38
+ updateTheme(theme) {
39
+ this.config.update(cfg => ({
40
+ ...cfg,
41
+ theme: { ...cfg.theme, ...theme }
42
+ }));
43
+ }
44
+ reset() {
45
+ this.config.set({});
46
+ this.setThemeMode('light');
51
47
  }
52
48
  applyTheme(theme) {
53
49
  if (!theme)
54
50
  return;
55
51
  const root = this.document.documentElement;
56
- // Apply colors
57
52
  if (theme.colors) {
58
53
  this.applyColorTokens(root, theme.colors);
59
54
  }
60
- // Apply typography
61
55
  if (theme.typography) {
62
56
  if (theme.typography.fontFamily) {
63
57
  root.style.setProperty('--studio-font-family', theme.typography.fontFamily);
@@ -75,29 +69,23 @@ class StudioConfigService {
75
69
  this.applyTokens(root, 'studio-line-height', theme.typography.lineHeight);
76
70
  }
77
71
  }
78
- // Apply spacing
79
72
  if (theme.spacing) {
80
73
  this.applyTokens(root, 'studio-spacing', theme.spacing);
81
74
  }
82
- // Apply border radius
83
75
  if (theme.borderRadius) {
84
76
  this.applyTokens(root, 'studio-radius', theme.borderRadius);
85
77
  }
86
- // Apply shadows
87
78
  if (theme.shadows) {
88
79
  this.applyTokens(root, 'studio-shadow', theme.shadows);
89
80
  }
90
- // Apply transitions
91
81
  if (theme.transitions) {
92
82
  this.applyTokens(root, 'studio-transition', theme.transitions);
93
83
  }
94
- // Apply z-index
95
84
  if (theme.zIndex) {
96
85
  this.applyZIndexTokens(root, theme.zIndex);
97
86
  }
98
87
  }
99
88
  applyColorTokens(root, colors) {
100
- // Brand colors with hover/active states
101
89
  const brandColors = ['primary', 'secondary', 'success', 'error', 'warning', 'info'];
102
90
  brandColors.forEach(colorName => {
103
91
  const colorValue = colors[colorName];
@@ -122,15 +110,12 @@ class StudioConfigService {
122
110
  }
123
111
  }
124
112
  });
125
- // Background colors
126
113
  if (colors.bg) {
127
114
  this.applyTokens(root, 'studio-bg', colors.bg);
128
115
  }
129
- // Text colors
130
116
  if (colors.text) {
131
117
  this.applyTokens(root, 'studio-text', colors.text);
132
118
  }
133
- // Border colors
134
119
  if (colors.border) {
135
120
  this.applyTokens(root, 'studio-border', colors.border);
136
121
  }
@@ -212,17 +197,48 @@ function provideStudioConfig(config = {}) {
212
197
  * @module config
213
198
  */
214
199
 
200
+ /**
201
+ * Icon component with Lucide icons support
202
+ *
203
+ * @example
204
+ * <studio-icon name="check" size="24" color="primary" />
205
+ */
215
206
  class IconComponent {
216
207
  name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
217
208
  size = input(24, ...(ngDevMode ? [{ debugName: "size" }] : []));
218
209
  color = input('inherit', ...(ngDevMode ? [{ debugName: "color" }] : []));
219
210
  strokeWidth = input(2, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : []));
220
211
  absoluteStrokeWidth = input(false, ...(ngDevMode ? [{ debugName: "absoluteStrokeWidth" }] : []));
212
+ showFallback = input(true, ...(ngDevMode ? [{ debugName: "showFallback" }] : []));
213
+ fallbackIcon = input('help-circle', ...(ngDevMode ? [{ debugName: "fallbackIcon" }] : []));
221
214
  icons = icons;
222
215
  lucideIcon = computed(() => {
223
216
  const iconName = this.name();
224
- return iconName;
217
+ if (iconName && icons[iconName]) {
218
+ return iconName;
219
+ }
220
+ const pascalCaseName = this.kebabToPascalCase(iconName);
221
+ if (pascalCaseName && icons[pascalCaseName]) {
222
+ return pascalCaseName;
223
+ }
224
+ if (this.showFallback()) {
225
+ const fallback = this.fallbackIcon();
226
+ if (icons[fallback]) {
227
+ return fallback;
228
+ }
229
+ const pascalCaseFallback = this.kebabToPascalCase(fallback);
230
+ if (pascalCaseFallback && icons[pascalCaseFallback]) {
231
+ return pascalCaseFallback;
232
+ }
233
+ }
234
+ return null;
225
235
  }, ...(ngDevMode ? [{ debugName: "lucideIcon" }] : []));
236
+ kebabToPascalCase(str) {
237
+ return str
238
+ .split('-')
239
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
240
+ .join('');
241
+ }
226
242
  computedColor = computed(() => {
227
243
  const colorValue = this.color();
228
244
  const colorMap = {
@@ -239,7 +255,7 @@ class IconComponent {
239
255
  return ['studio-icon'];
240
256
  }, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
241
257
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
242
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: IconComponent, isStandalone: true, selector: "studio-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, absoluteStrokeWidth: { classPropertyName: "absoluteStrokeWidth", publicName: "absoluteStrokeWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "style.width.px": "size()", "style.height.px": "size()" } }, ngImport: i0, template: `
258
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: IconComponent, isStandalone: true, selector: "studio-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, absoluteStrokeWidth: { classPropertyName: "absoluteStrokeWidth", publicName: "absoluteStrokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showFallback: { classPropertyName: "showFallback", publicName: "showFallback", isSignal: true, isRequired: false, transformFunction: null }, fallbackIcon: { classPropertyName: "fallbackIcon", publicName: "fallbackIcon", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "style.width.px": "size()", "style.height.px": "size()" } }, ngImport: i0, template: `
243
259
  @if (lucideIcon(); as iconName) {
244
260
  <lucide-icon
245
261
  [name]="iconName"
@@ -268,11 +284,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
268
284
  />
269
285
  }
270
286
  `, styles: [":host{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}\n"] }]
271
- }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], absoluteStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "absoluteStrokeWidth", required: false }] }] } });
287
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], absoluteStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "absoluteStrokeWidth", required: false }] }], showFallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFallback", required: false }] }], fallbackIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackIcon", required: false }] }] } });
272
288
 
273
289
  /**
274
- * Провайдер иконок для Studio
275
- * Регистрирует базовый набор часто используемых иконок
290
+ * Studio icon provider
291
+ * Registers commonly used Lucide icons
276
292
  *
277
293
  * @example
278
294
  * ```typescript
@@ -286,7 +302,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
286
302
  * };
287
303
  * ```
288
304
  *
289
- * Для добавления дополнительных иконок:
305
+ * To add additional icons:
290
306
  * @example
291
307
  * ```typescript
292
308
  * import { LUCIDE_ICONS, LucideIconProvider } from 'lucide-angular';
@@ -362,48 +378,354 @@ function provideStudioIcons() {
362
378
  RefreshCw,
363
379
  RotateCw,
364
380
  Loader,
365
- Loader2
381
+ Loader2,
382
+ ShoppingCart,
383
+ Sun,
384
+ Moon
366
385
  })
367
386
  }
368
387
  ]);
369
388
  }
370
389
 
390
+ /**
391
+ * Checks if URL is safe for navigation
392
+ *
393
+ * @param url - URL to check
394
+ * @returns true if URL is safe (http/https or relative)
395
+ *
396
+ * @example
397
+ * ```typescript
398
+ * isSafeUrl('https://example.com') // true
399
+ * isSafeUrl('javascript:alert(1)') // false
400
+ * isSafeUrl('/relative/path') // true
401
+ * ```
402
+ */
403
+ function isSafeUrl(url) {
404
+ if (!url || typeof url !== 'string') {
405
+ return false;
406
+ }
407
+ try {
408
+ const parsed = new URL(url, window.location.origin);
409
+ return ['http:', 'https:', ''].includes(parsed.protocol);
410
+ }
411
+ catch {
412
+ return false;
413
+ }
414
+ }
415
+ /**
416
+ * Sanitize URL before using in navigation
417
+ *
418
+ * @param url - URL to sanitize
419
+ * @returns Safe URL or '#' if URL is unsafe
420
+ *
421
+ * @example
422
+ * ```typescript
423
+ * sanitizeUrl('https://example.com') // 'https://example.com'
424
+ * sanitizeUrl('javascript:alert(1)') // '#' (with console warning)
425
+ * ```
426
+ */
427
+ function sanitizeUrl(url) {
428
+ if (!isSafeUrl(url)) {
429
+ console.warn(`[Studio] Unsafe URL blocked: ${url}`);
430
+ return '#';
431
+ }
432
+ return url;
433
+ }
434
+
435
+ /**
436
+ * Combines CSS classes, ignoring falsy values
437
+ *
438
+ * @param classes - Array of classes (strings, undefined, null, false)
439
+ * @returns String with combined classes
440
+ *
441
+ * @example
442
+ * ```typescript
443
+ * classNames('btn', 'btn-primary') // 'btn btn-primary'
444
+ * classNames('btn', isActive && 'active') // 'btn active' if isActive=true, else 'btn'
445
+ * classNames('btn', undefined, null, false, 'large') // 'btn large'
446
+ * ```
447
+ */
448
+ function classNames(...classes) {
449
+ return classes.filter(Boolean).join(' ');
450
+ }
451
+
452
+ /**
453
+ * Creates computed signal with fallback chain: input → config → default
454
+ *
455
+ * @param inputSignal - User input signal
456
+ * @param configSignal - Config value (can be undefined)
457
+ * @param defaultValue - Fallback value
458
+ * @returns Computed signal with resolved value
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * export class ButtonComponent {
463
+ * private defaults = computed(() => this.configService.config().components?.button);
464
+ *
465
+ * variantInput = input<Variant | undefined>(undefined, { alias: 'variant' });
466
+ *
467
+ * variant = withConfigDefault(
468
+ * this.variantInput,
469
+ * computed(() => this.defaults()?.variant),
470
+ * 'solid'
471
+ * );
472
+ * }
473
+ * ```
474
+ */
475
+ function withConfigDefault(inputSignal, configSignal, defaultValue) {
476
+ return computed(() => {
477
+ const inputValue = inputSignal();
478
+ if (inputValue !== undefined)
479
+ return inputValue;
480
+ const configValue = configSignal();
481
+ if (configValue !== undefined)
482
+ return configValue;
483
+ return defaultValue;
484
+ });
485
+ }
486
+
487
+ /**
488
+ * @eduboxpro/studio - Utilities
489
+ *
490
+ * Helper functions and utilities for the Studio library
491
+ */
492
+
493
+ class BadgeComponent {
494
+ configService = inject(StudioConfigService);
495
+ badgeDefaults = computed(() => this.configService.config().components?.badge, ...(ngDevMode ? [{ debugName: "badgeDefaults" }] : []));
496
+ variantInput = input(undefined, ...(ngDevMode ? [{ debugName: "variantInput", alias: 'variant' }] : [{ alias: 'variant' }]));
497
+ sizeInput = input(undefined, ...(ngDevMode ? [{ debugName: "sizeInput", alias: 'size' }] : [{ alias: 'size' }]));
498
+ colorInput = input(undefined, ...(ngDevMode ? [{ debugName: "colorInput", alias: 'color' }] : [{ alias: 'color' }]));
499
+ radiusInput = input(undefined, ...(ngDevMode ? [{ debugName: "radiusInput", alias: 'radius' }] : [{ alias: 'radius' }]));
500
+ variant = withConfigDefault(this.variantInput, computed(() => this.badgeDefaults()?.variant), 'solid');
501
+ size = withConfigDefault(this.sizeInput, computed(() => this.badgeDefaults()?.size), 'md');
502
+ color = withConfigDefault(this.colorInput, computed(() => this.badgeDefaults()?.color), 'primary');
503
+ radius = withConfigDefault(this.radiusInput, computed(() => this.badgeDefaults()?.radius), 'full');
504
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : []));
505
+ iconPosition = input('left', ...(ngDevMode ? [{ debugName: "iconPosition" }] : []));
506
+ dot = input(false, ...(ngDevMode ? [{ debugName: "dot" }] : []));
507
+ dotColor = input(...(ngDevMode ? [undefined, { debugName: "dotColor" }] : []));
508
+ removable = input(false, ...(ngDevMode ? [{ debugName: "removable" }] : []));
509
+ removed = output();
510
+ href = input(...(ngDevMode ? [undefined, { debugName: "href" }] : []));
511
+ target = input('_self', ...(ngDevMode ? [{ debugName: "target" }] : []));
512
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
513
+ clicked = output();
514
+ value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
515
+ max = input(...(ngDevMode ? [undefined, { debugName: "max" }] : []));
516
+ showZero = input(true, ...(ngDevMode ? [{ debugName: "showZero" }] : []));
517
+ uppercase = input(false, ...(ngDevMode ? [{ debugName: "uppercase" }] : []));
518
+ bold = input(false, ...(ngDevMode ? [{ debugName: "bold" }] : []));
519
+ pulse = input(false, ...(ngDevMode ? [{ debugName: "pulse" }] : []));
520
+ autoColor = input(false, ...(ngDevMode ? [{ debugName: "autoColor" }] : []));
521
+ iconSize = computed(() => {
522
+ const sizeMap = { sm: 12, md: 14, lg: 16 };
523
+ return sizeMap[this.size()];
524
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
525
+ displayValue = computed(() => {
526
+ const val = this.value();
527
+ if (val === undefined || val === null)
528
+ return '';
529
+ const numVal = typeof val === 'number' ? val : parseInt(val, 10);
530
+ if (!isNaN(numVal)) {
531
+ if (numVal === 0 && !this.showZero())
532
+ return '';
533
+ const maxVal = this.max();
534
+ if (maxVal !== undefined && numVal > maxVal) {
535
+ return `${maxVal}+`;
536
+ }
537
+ return numVal.toString();
538
+ }
539
+ return val.toString();
540
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
541
+ isVisible = computed(() => {
542
+ const val = this.value();
543
+ if (val !== undefined && val !== null) {
544
+ const numVal = typeof val === 'number' ? val : parseInt(val, 10);
545
+ if (!isNaN(numVal) && numVal === 0 && !this.showZero()) {
546
+ return false;
547
+ }
548
+ }
549
+ return true;
550
+ }, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
551
+ hostClasses = computed(() => classNames('studio-badge', `studio-badge--${this.variant()}`, `studio-badge--${this.size()}`, `studio-badge--${this.color()}`, `studio-badge--radius-${this.radius()}`, this.disabled() && 'studio-badge--disabled', this.uppercase() && 'studio-badge--uppercase', this.bold() && 'studio-badge--bold', this.pulse() && 'studio-badge--pulse', this.href() && 'studio-badge--clickable'), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
552
+ handleClick(event) {
553
+ if (this.disabled()) {
554
+ event.preventDefault();
555
+ event.stopPropagation();
556
+ return;
557
+ }
558
+ const url = this.href();
559
+ if (url) {
560
+ const safeUrl = sanitizeUrl(url);
561
+ if (safeUrl === '#') {
562
+ return;
563
+ }
564
+ const target = this.target();
565
+ if (target === '_blank') {
566
+ window.open(safeUrl, '_blank', 'noopener,noreferrer');
567
+ }
568
+ else {
569
+ window.location.href = safeUrl;
570
+ }
571
+ }
572
+ this.clicked.emit(event);
573
+ }
574
+ handleRemove(event) {
575
+ event.preventDefault();
576
+ event.stopPropagation();
577
+ if (this.disabled())
578
+ return;
579
+ this.removed.emit();
580
+ }
581
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: BadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
582
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: BadgeComponent, isStandalone: true, selector: "studio-badge", inputs: { variantInput: { classPropertyName: "variantInput", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, sizeInput: { classPropertyName: "sizeInput", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, colorInput: { classPropertyName: "colorInput", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, radiusInput: { classPropertyName: "radiusInput", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null }, dotColor: { classPropertyName: "dotColor", publicName: "dotColor", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, showZero: { classPropertyName: "showZero", publicName: "showZero", isSignal: true, isRequired: false, transformFunction: null }, uppercase: { classPropertyName: "uppercase", publicName: "uppercase", isSignal: true, isRequired: false, transformFunction: null }, bold: { classPropertyName: "bold", publicName: "bold", isSignal: true, isRequired: false, transformFunction: null }, pulse: { classPropertyName: "pulse", publicName: "pulse", isSignal: true, isRequired: false, transformFunction: null }, autoColor: { classPropertyName: "autoColor", publicName: "autoColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed", clicked: "clicked" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "hostClasses()", "attr.disabled": "disabled() ? \"\" : null", "attr.href": "href()", "attr.target": "href() ? target() : null", "attr.rel": "href() && target() === \"_blank\" ? \"noopener noreferrer\" : null", "style.display": "isVisible() ? null : \"none\"" } }, ngImport: i0, template: `
583
+ <span class="studio-badge__content">
584
+ @if (dot()) {
585
+ <span
586
+ class="studio-badge__dot"
587
+ [class]="'studio-badge__dot--' + (dotColor() || color())"
588
+ ></span>
589
+ }
590
+ @if (icon() && iconPosition() === 'left') {
591
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
592
+ }
593
+ <span class="studio-badge__text">
594
+ @if (displayValue()) {
595
+ {{ displayValue() }}
596
+ } @else {
597
+ <ng-content />
598
+ }
599
+ </span>
600
+ @if (icon() && iconPosition() === 'right') {
601
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
602
+ }
603
+ @if (removable()) {
604
+ <button
605
+ type="button"
606
+ class="studio-badge__remove"
607
+ (click)="handleRemove($event)"
608
+ aria-label="Remove"
609
+ >
610
+ <studio-icon name="x" [size]="iconSize()" />
611
+ </button>
612
+ }
613
+ </span>
614
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;font-family:var(--studio-font-family);font-weight:var(--studio-badge-font-weight, 500);line-height:1;white-space:nowrap;transition:all .2s ease;cursor:default;-webkit-user-select:none;user-select:none}.studio-badge__content{display:inline-flex;align-items:center;gap:var(--studio-badge-gap);padding:var(--studio-badge-padding-y) var(--studio-badge-padding-x);border-radius:var(--studio-badge-radius);background:var(--studio-badge-bg);color:var(--studio-badge-color);border:var(--studio-badge-border-width, 1px) solid var(--studio-badge-border-color, transparent);font-size:var(--studio-badge-font-size)}.studio-badge__text{display:inline-block}.studio-badge__dot{width:var(--studio-badge-dot-size, 6px);height:var(--studio-badge-dot-size, 6px);border-radius:50%;background:var(--studio-badge-dot-bg);flex-shrink:0}.studio-badge__remove{all:unset;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;opacity:.7;transition:opacity .2s ease}.studio-badge__remove:hover{opacity:1}.studio-badge__remove:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:2px}:host(.studio-badge--solid){--studio-badge-bg: var(--studio-primary);--studio-badge-color: white}:host(.studio-badge--solid).studio-badge--primary{--studio-badge-bg: var(--studio-primary)}:host(.studio-badge--solid).studio-badge--secondary{--studio-badge-bg: var(--studio-secondary)}:host(.studio-badge--solid).studio-badge--success{--studio-badge-bg: var(--studio-success)}:host(.studio-badge--solid).studio-badge--error{--studio-badge-bg: var(--studio-error)}:host(.studio-badge--solid).studio-badge--warning{--studio-badge-bg: var(--studio-warning)}:host(.studio-badge--solid).studio-badge--info{--studio-badge-bg: var(--studio-info)}:host(.studio-badge--solid).studio-badge--neutral{--studio-badge-bg: var(--studio-border-secondary);--studio-badge-color: var(--studio-text-primary)}:host(.studio-badge--outline){--studio-badge-bg: transparent;--studio-badge-color: var(--studio-primary);--studio-badge-border-color: var(--studio-primary)}:host(.studio-badge--outline).studio-badge--primary{--studio-badge-color: var(--studio-primary);--studio-badge-border-color: var(--studio-primary)}:host(.studio-badge--outline).studio-badge--secondary{--studio-badge-color: var(--studio-secondary);--studio-badge-border-color: var(--studio-secondary)}:host(.studio-badge--outline).studio-badge--success{--studio-badge-color: var(--studio-success);--studio-badge-border-color: var(--studio-success)}:host(.studio-badge--outline).studio-badge--error{--studio-badge-color: var(--studio-error);--studio-badge-border-color: var(--studio-error)}:host(.studio-badge--outline).studio-badge--warning{--studio-badge-color: var(--studio-warning);--studio-badge-border-color: var(--studio-warning)}:host(.studio-badge--outline).studio-badge--info{--studio-badge-color: var(--studio-info);--studio-badge-border-color: var(--studio-info)}:host(.studio-badge--outline).studio-badge--neutral{--studio-badge-color: var(--studio-text-secondary);--studio-badge-border-color: var(--studio-border-secondary)}:host(.studio-badge--soft){--studio-badge-bg: rgba(124, 58, 237, .1);--studio-badge-color: var(--studio-primary)}:host(.studio-badge--soft).studio-badge--primary{--studio-badge-bg: rgba(124, 58, 237, .1);--studio-badge-color: var(--studio-primary)}:host(.studio-badge--soft).studio-badge--secondary{--studio-badge-bg: rgba(99, 102, 241, .1);--studio-badge-color: var(--studio-secondary)}:host(.studio-badge--soft).studio-badge--success{--studio-badge-bg: var(--studio-success-bg);--studio-badge-color: var(--studio-success)}:host(.studio-badge--soft).studio-badge--error{--studio-badge-bg: var(--studio-error-bg);--studio-badge-color: var(--studio-error)}:host(.studio-badge--soft).studio-badge--warning{--studio-badge-bg: var(--studio-warning-bg);--studio-badge-color: var(--studio-warning)}:host(.studio-badge--soft).studio-badge--info{--studio-badge-bg: var(--studio-info-bg);--studio-badge-color: var(--studio-info)}:host(.studio-badge--soft).studio-badge--neutral{--studio-badge-bg: var(--studio-bg-secondary);--studio-badge-color: var(--studio-text-secondary)}:host(.studio-badge--dot){--studio-badge-bg: transparent;--studio-badge-color: var(--studio-text-primary);--studio-badge-border-width: 0}:host(.studio-badge--sm){--studio-badge-padding-y: .125rem;--studio-badge-padding-x: .5rem;--studio-badge-font-size: .6875rem;--studio-badge-gap: .25rem}:host(.studio-badge--md){--studio-badge-padding-y: .25rem;--studio-badge-padding-x: .625rem;--studio-badge-font-size: .75rem;--studio-badge-gap: .375rem}:host(.studio-badge--lg){--studio-badge-padding-y: .375rem;--studio-badge-padding-x: .75rem;--studio-badge-font-size: .875rem;--studio-badge-gap: .5rem}:host(.studio-badge--radius-sm){--studio-badge-radius: 2px}:host(.studio-badge--radius-md){--studio-badge-radius: 4px}:host(.studio-badge--radius-lg){--studio-badge-radius: 8px}:host(.studio-badge--radius-full){--studio-badge-radius: 9999px}.studio-badge__dot--primary{--studio-badge-dot-bg: var(--studio-primary)}.studio-badge__dot--secondary{--studio-badge-dot-bg: var(--studio-secondary)}.studio-badge__dot--success{--studio-badge-dot-bg: var(--studio-success)}.studio-badge__dot--error{--studio-badge-dot-bg: var(--studio-error)}.studio-badge__dot--warning{--studio-badge-dot-bg: var(--studio-warning)}.studio-badge__dot--info{--studio-badge-dot-bg: var(--studio-info)}.studio-badge__dot--neutral{--studio-badge-dot-bg: var(--studio-text-secondary)}:host(.studio-badge--clickable){cursor:pointer}:host(.studio-badge--clickable):hover{opacity:.9;transform:translateY(-1px)}:host(.studio-badge--clickable):active{transform:translateY(0)}:host(.studio-badge--disabled){opacity:.5;cursor:not-allowed;pointer-events:none}:host(.studio-badge--uppercase){text-transform:uppercase;letter-spacing:.5px}:host(.studio-badge--bold){--studio-badge-font-weight: 600}@keyframes badge-pulse{0%,to{opacity:1}50%{opacity:.5}}:host(.studio-badge--pulse) .studio-badge__dot{animation:badge-pulse 2s cubic-bezier(.4,0,.6,1) infinite}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "studio-icon", inputs: ["name", "size", "color", "strokeWidth", "absoluteStrokeWidth", "showFallback", "fallbackIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
615
+ }
616
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: BadgeComponent, decorators: [{
617
+ type: Component,
618
+ args: [{ selector: 'studio-badge', standalone: true, imports: [IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
619
+ '[class]': 'hostClasses()',
620
+ '[attr.disabled]': 'disabled() ? "" : null',
621
+ '[attr.href]': 'href()',
622
+ '[attr.target]': 'href() ? target() : null',
623
+ '[attr.rel]': 'href() && target() === "_blank" ? "noopener noreferrer" : null',
624
+ '[style.display]': 'isVisible() ? null : "none"',
625
+ '(click)': 'handleClick($event)'
626
+ }, template: `
627
+ <span class="studio-badge__content">
628
+ @if (dot()) {
629
+ <span
630
+ class="studio-badge__dot"
631
+ [class]="'studio-badge__dot--' + (dotColor() || color())"
632
+ ></span>
633
+ }
634
+ @if (icon() && iconPosition() === 'left') {
635
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
636
+ }
637
+ <span class="studio-badge__text">
638
+ @if (displayValue()) {
639
+ {{ displayValue() }}
640
+ } @else {
641
+ <ng-content />
642
+ }
643
+ </span>
644
+ @if (icon() && iconPosition() === 'right') {
645
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
646
+ }
647
+ @if (removable()) {
648
+ <button
649
+ type="button"
650
+ class="studio-badge__remove"
651
+ (click)="handleRemove($event)"
652
+ aria-label="Remove"
653
+ >
654
+ <studio-icon name="x" [size]="iconSize()" />
655
+ </button>
656
+ }
657
+ </span>
658
+ `, styles: [":host{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;font-family:var(--studio-font-family);font-weight:var(--studio-badge-font-weight, 500);line-height:1;white-space:nowrap;transition:all .2s ease;cursor:default;-webkit-user-select:none;user-select:none}.studio-badge__content{display:inline-flex;align-items:center;gap:var(--studio-badge-gap);padding:var(--studio-badge-padding-y) var(--studio-badge-padding-x);border-radius:var(--studio-badge-radius);background:var(--studio-badge-bg);color:var(--studio-badge-color);border:var(--studio-badge-border-width, 1px) solid var(--studio-badge-border-color, transparent);font-size:var(--studio-badge-font-size)}.studio-badge__text{display:inline-block}.studio-badge__dot{width:var(--studio-badge-dot-size, 6px);height:var(--studio-badge-dot-size, 6px);border-radius:50%;background:var(--studio-badge-dot-bg);flex-shrink:0}.studio-badge__remove{all:unset;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;opacity:.7;transition:opacity .2s ease}.studio-badge__remove:hover{opacity:1}.studio-badge__remove:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:2px}:host(.studio-badge--solid){--studio-badge-bg: var(--studio-primary);--studio-badge-color: white}:host(.studio-badge--solid).studio-badge--primary{--studio-badge-bg: var(--studio-primary)}:host(.studio-badge--solid).studio-badge--secondary{--studio-badge-bg: var(--studio-secondary)}:host(.studio-badge--solid).studio-badge--success{--studio-badge-bg: var(--studio-success)}:host(.studio-badge--solid).studio-badge--error{--studio-badge-bg: var(--studio-error)}:host(.studio-badge--solid).studio-badge--warning{--studio-badge-bg: var(--studio-warning)}:host(.studio-badge--solid).studio-badge--info{--studio-badge-bg: var(--studio-info)}:host(.studio-badge--solid).studio-badge--neutral{--studio-badge-bg: var(--studio-border-secondary);--studio-badge-color: var(--studio-text-primary)}:host(.studio-badge--outline){--studio-badge-bg: transparent;--studio-badge-color: var(--studio-primary);--studio-badge-border-color: var(--studio-primary)}:host(.studio-badge--outline).studio-badge--primary{--studio-badge-color: var(--studio-primary);--studio-badge-border-color: var(--studio-primary)}:host(.studio-badge--outline).studio-badge--secondary{--studio-badge-color: var(--studio-secondary);--studio-badge-border-color: var(--studio-secondary)}:host(.studio-badge--outline).studio-badge--success{--studio-badge-color: var(--studio-success);--studio-badge-border-color: var(--studio-success)}:host(.studio-badge--outline).studio-badge--error{--studio-badge-color: var(--studio-error);--studio-badge-border-color: var(--studio-error)}:host(.studio-badge--outline).studio-badge--warning{--studio-badge-color: var(--studio-warning);--studio-badge-border-color: var(--studio-warning)}:host(.studio-badge--outline).studio-badge--info{--studio-badge-color: var(--studio-info);--studio-badge-border-color: var(--studio-info)}:host(.studio-badge--outline).studio-badge--neutral{--studio-badge-color: var(--studio-text-secondary);--studio-badge-border-color: var(--studio-border-secondary)}:host(.studio-badge--soft){--studio-badge-bg: rgba(124, 58, 237, .1);--studio-badge-color: var(--studio-primary)}:host(.studio-badge--soft).studio-badge--primary{--studio-badge-bg: rgba(124, 58, 237, .1);--studio-badge-color: var(--studio-primary)}:host(.studio-badge--soft).studio-badge--secondary{--studio-badge-bg: rgba(99, 102, 241, .1);--studio-badge-color: var(--studio-secondary)}:host(.studio-badge--soft).studio-badge--success{--studio-badge-bg: var(--studio-success-bg);--studio-badge-color: var(--studio-success)}:host(.studio-badge--soft).studio-badge--error{--studio-badge-bg: var(--studio-error-bg);--studio-badge-color: var(--studio-error)}:host(.studio-badge--soft).studio-badge--warning{--studio-badge-bg: var(--studio-warning-bg);--studio-badge-color: var(--studio-warning)}:host(.studio-badge--soft).studio-badge--info{--studio-badge-bg: var(--studio-info-bg);--studio-badge-color: var(--studio-info)}:host(.studio-badge--soft).studio-badge--neutral{--studio-badge-bg: var(--studio-bg-secondary);--studio-badge-color: var(--studio-text-secondary)}:host(.studio-badge--dot){--studio-badge-bg: transparent;--studio-badge-color: var(--studio-text-primary);--studio-badge-border-width: 0}:host(.studio-badge--sm){--studio-badge-padding-y: .125rem;--studio-badge-padding-x: .5rem;--studio-badge-font-size: .6875rem;--studio-badge-gap: .25rem}:host(.studio-badge--md){--studio-badge-padding-y: .25rem;--studio-badge-padding-x: .625rem;--studio-badge-font-size: .75rem;--studio-badge-gap: .375rem}:host(.studio-badge--lg){--studio-badge-padding-y: .375rem;--studio-badge-padding-x: .75rem;--studio-badge-font-size: .875rem;--studio-badge-gap: .5rem}:host(.studio-badge--radius-sm){--studio-badge-radius: 2px}:host(.studio-badge--radius-md){--studio-badge-radius: 4px}:host(.studio-badge--radius-lg){--studio-badge-radius: 8px}:host(.studio-badge--radius-full){--studio-badge-radius: 9999px}.studio-badge__dot--primary{--studio-badge-dot-bg: var(--studio-primary)}.studio-badge__dot--secondary{--studio-badge-dot-bg: var(--studio-secondary)}.studio-badge__dot--success{--studio-badge-dot-bg: var(--studio-success)}.studio-badge__dot--error{--studio-badge-dot-bg: var(--studio-error)}.studio-badge__dot--warning{--studio-badge-dot-bg: var(--studio-warning)}.studio-badge__dot--info{--studio-badge-dot-bg: var(--studio-info)}.studio-badge__dot--neutral{--studio-badge-dot-bg: var(--studio-text-secondary)}:host(.studio-badge--clickable){cursor:pointer}:host(.studio-badge--clickable):hover{opacity:.9;transform:translateY(-1px)}:host(.studio-badge--clickable):active{transform:translateY(0)}:host(.studio-badge--disabled){opacity:.5;cursor:not-allowed;pointer-events:none}:host(.studio-badge--uppercase){text-transform:uppercase;letter-spacing:.5px}:host(.studio-badge--bold){--studio-badge-font-weight: 600}@keyframes badge-pulse{0%,to{opacity:1}50%{opacity:.5}}:host(.studio-badge--pulse) .studio-badge__dot{animation:badge-pulse 2s cubic-bezier(.4,0,.6,1) infinite}\n"] }]
659
+ }], propDecorators: { variantInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], sizeInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], colorInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], radiusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPosition", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }], dotColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotColor", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], showZero: [{ type: i0.Input, args: [{ isSignal: true, alias: "showZero", required: false }] }], uppercase: [{ type: i0.Input, args: [{ isSignal: true, alias: "uppercase", required: false }] }], bold: [{ type: i0.Input, args: [{ isSignal: true, alias: "bold", required: false }] }], pulse: [{ type: i0.Input, args: [{ isSignal: true, alias: "pulse", required: false }] }], autoColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoColor", required: false }] }] } });
660
+
661
+ /**
662
+ * Badge component
663
+ */
664
+
665
+ /**
666
+ * Button component with multiple variants, sizes, colors and states
667
+ *
668
+ * @example
669
+ * <studio-button variant="solid" color="primary" (clicked)="onClick()">
670
+ * Click me
671
+ * </studio-button>
672
+ */
371
673
  class ButtonComponent {
372
- variant = input('solid', ...(ngDevMode ? [{ debugName: "variant" }] : []));
373
- size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
374
- color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : []));
674
+ configService = inject(StudioConfigService);
675
+ buttonDefaults = computed(() => this.configService.config().components?.button, ...(ngDevMode ? [{ debugName: "buttonDefaults" }] : []));
676
+ variantInput = input(undefined, ...(ngDevMode ? [{ debugName: "variantInput", alias: 'variant' }] : [{ alias: 'variant' }]));
677
+ sizeInput = input(undefined, ...(ngDevMode ? [{ debugName: "sizeInput", alias: 'size' }] : [{ alias: 'size' }]));
678
+ colorInput = input(undefined, ...(ngDevMode ? [{ debugName: "colorInput", alias: 'color' }] : [{ alias: 'color' }]));
679
+ radiusInput = input(undefined, ...(ngDevMode ? [{ debugName: "radiusInput", alias: 'radius' }] : [{ alias: 'radius' }]));
680
+ shadowInput = input(undefined, ...(ngDevMode ? [{ debugName: "shadowInput", alias: 'shadow' }] : [{ alias: 'shadow' }]));
681
+ compactInput = input(undefined, ...(ngDevMode ? [{ debugName: "compactInput", alias: 'compact' }] : [{ alias: 'compact' }]));
682
+ variant = withConfigDefault(this.variantInput, computed(() => this.buttonDefaults()?.variant), 'solid');
683
+ size = withConfigDefault(this.sizeInput, computed(() => this.buttonDefaults()?.size), 'md');
684
+ color = withConfigDefault(this.colorInput, computed(() => this.buttonDefaults()?.color), 'primary');
685
+ radius = withConfigDefault(this.radiusInput, computed(() => this.buttonDefaults()?.radius), 'sm');
686
+ shadow = withConfigDefault(this.shadowInput, computed(() => this.buttonDefaults()?.shadow), 'none');
687
+ compact = withConfigDefault(this.compactInput, computed(() => this.buttonDefaults()?.compact), false);
375
688
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
376
- fullWidth = input(false, ...(ngDevMode ? [{ debugName: "fullWidth" }] : []));
377
689
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
690
+ loadingText = input('Loading...', ...(ngDevMode ? [{ debugName: "loadingText" }] : []));
691
+ fullWidth = input(false, ...(ngDevMode ? [{ debugName: "fullWidth" }] : []));
378
692
  type = input('button', ...(ngDevMode ? [{ debugName: "type" }] : []));
379
693
  icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : []));
380
694
  iconPosition = input('left', ...(ngDevMode ? [{ debugName: "iconPosition" }] : []));
695
+ href = input(...(ngDevMode ? [undefined, { debugName: "href" }] : []));
696
+ target = input('_self', ...(ngDevMode ? [{ debugName: "target" }] : []));
697
+ badge = input(...(ngDevMode ? [undefined, { debugName: "badge" }] : []));
698
+ badgeColor = input('error', ...(ngDevMode ? [{ debugName: "badgeColor" }] : []));
699
+ ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : []));
381
700
  clicked = output();
382
701
  iconSize = computed(() => {
383
702
  const sizeMap = { sm: 16, md: 18, lg: 20 };
384
703
  return sizeMap[this.size()];
385
704
  }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
386
- hostClasses = computed(() => {
387
- return [
388
- 'studio-button',
389
- `studio-button--${this.variant()}`,
390
- `studio-button--${this.size()}`,
391
- `studio-button--${this.color()}`,
392
- this.fullWidth() ? 'studio-button--full' : '',
393
- this.loading() ? 'studio-button--loading' : '',
394
- this.iconPosition() === 'only' ? 'studio-button--icon-only' : ''
395
- ].filter(Boolean).join(' ');
396
- }, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
705
+ hostClasses = computed(() => classNames('studio-button', `studio-button--${this.variant()}`, `studio-button--${this.size()}`, `studio-button--${this.color()}`, `studio-button--radius-${this.radius()}`, `studio-button--shadow-${this.shadow()}`, this.compact() && 'studio-button--compact', this.fullWidth() && 'studio-button--full', this.loading() && 'studio-button--loading', this.iconPosition() === 'only' && 'studio-button--icon-only'), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
397
706
  handleClick(event) {
398
707
  if (this.disabled() || this.loading()) {
399
708
  event.preventDefault();
400
709
  event.stopPropagation();
401
710
  return;
402
711
  }
712
+ const url = this.href();
713
+ if (url) {
714
+ const safeUrl = sanitizeUrl(url);
715
+ if (safeUrl === '#')
716
+ return;
717
+ const target = this.target();
718
+ if (target === '_blank') {
719
+ window.open(safeUrl, '_blank', 'noopener,noreferrer');
720
+ }
721
+ else {
722
+ window.location.href = safeUrl;
723
+ }
724
+ }
403
725
  this.clicked.emit(event);
404
726
  }
405
727
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
406
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: ButtonComponent, isStandalone: true, selector: "studio-button", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "hostClasses()", "attr.disabled": "disabled() || loading() ? \"\" : null", "attr.type": "type()" } }, ngImport: i0, template: `
728
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: ButtonComponent, isStandalone: true, selector: "studio-button", inputs: { variantInput: { classPropertyName: "variantInput", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, sizeInput: { classPropertyName: "sizeInput", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, colorInput: { classPropertyName: "colorInput", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, radiusInput: { classPropertyName: "radiusInput", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, shadowInput: { classPropertyName: "shadowInput", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null }, compactInput: { classPropertyName: "compactInput", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, badgeColor: { classPropertyName: "badgeColor", publicName: "badgeColor", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "hostClasses()", "attr.disabled": "disabled() || loading() ? \"\" : null", "attr.type": "href() ? null : type()", "attr.href": "href()", "attr.target": "href() ? target() : null", "attr.rel": "href() && target() === \"_blank\" ? \"noopener noreferrer\" : null", "attr.aria-label": "ariaLabel()" } }, ngImport: i0, template: `
407
729
  <span class="studio-button__content">
408
730
  @if (loading()) {
409
731
  <studio-icon
@@ -425,19 +747,27 @@ class ButtonComponent {
425
747
  <studio-icon [name]="icon()!" [size]="iconSize()" />
426
748
  }
427
749
  @if (loading() && iconPosition() === 'only') {
428
- <!-- Empty, spinner already shown -->
429
750
  } @else if (loading()) {
430
- <span>Loading...</span>
751
+ <span>{{ loadingText() }}</span>
752
+ }
753
+ @if (badge() !== undefined && badge() !== null && badge() !== '') {
754
+ <span class="studio-button__badge studio-button__badge--{{ badgeColor() }}">
755
+ {{ badge() }}
756
+ </span>
431
757
  }
432
758
  </span>
433
- `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;gap:var(--studio-spacing-sm);font-family:var(--studio-font-family);font-weight:var(--studio-font-weight-medium);border:none;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;border-radius:var(--studio-radius-md);transition:background-color var(--studio-transition-fast),color var(--studio-transition-fast),border-color var(--studio-transition-fast),box-shadow var(--studio-transition-fast),transform var(--studio-transition-fast)}:host:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}:host(.studio-button--sm){padding:var(--studio-spacing-xs) var(--studio-spacing-sm);font-size:var(--studio-font-size-sm);height:2rem;min-width:2rem}:host(.studio-button--md){padding:var(--studio-spacing-sm) var(--studio-spacing-md);font-size:var(--studio-font-size-base);height:2.5rem;min-width:2.5rem}:host(.studio-button--lg){padding:var(--studio-spacing-md) var(--studio-spacing-lg);font-size:var(--studio-font-size-lg);height:3rem;min-width:3rem}:host(.studio-button--solid.studio-button--primary){background:var(--studio-primary);color:#fff}:host(.studio-button--solid.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary-hover)}:host(.studio-button--outline.studio-button--primary){background:transparent;border:1px solid var(--studio-primary);color:var(--studio-primary)}:host(.studio-button--outline.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary);color:#fff}:host(.studio-button--ghost.studio-button--primary){background:transparent;color:var(--studio-primary)}:host(.studio-button--ghost.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:#3b82f61a}:host(.studio-button--solid.studio-button--secondary){background:var(--studio-secondary);color:#fff}:host(.studio-button--solid.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary-hover)}:host(.studio-button--outline.studio-button--secondary){background:transparent;border:1px solid var(--studio-secondary);color:var(--studio-secondary)}:host(.studio-button--outline.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary);color:#fff}:host(.studio-button--ghost.studio-button--secondary){background:transparent;color:var(--studio-secondary)}:host(.studio-button--ghost.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:#8b5cf61a}:host(.studio-button--solid.studio-button--success){background:var(--studio-success);color:#fff}:host(.studio-button--solid.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-hover)}:host(.studio-button--outline.studio-button--success){background:transparent;border:1px solid var(--studio-success);color:var(--studio-success)}:host(.studio-button--outline.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success);color:#fff}:host(.studio-button--ghost.studio-button--success){background:transparent;color:var(--studio-success)}:host(.studio-button--ghost.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-bg)}:host(.studio-button--solid.studio-button--error){background:var(--studio-error);color:#fff}:host(.studio-button--solid.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-hover)}:host(.studio-button--outline.studio-button--error){background:transparent;border:1px solid var(--studio-error);color:var(--studio-error)}:host(.studio-button--outline.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error);color:#fff}:host(.studio-button--ghost.studio-button--error){background:transparent;color:var(--studio-error)}:host(.studio-button--ghost.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-bg)}:host(.studio-button--solid.studio-button--warning){background:var(--studio-warning);color:#fff}:host(.studio-button--solid.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-hover)}:host(.studio-button--outline.studio-button--warning){background:transparent;border:1px solid var(--studio-warning);color:var(--studio-warning)}:host(.studio-button--outline.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning);color:#fff}:host(.studio-button--ghost.studio-button--warning){background:transparent;color:var(--studio-warning)}:host(.studio-button--ghost.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-bg)}:host(.studio-button--full){width:100%}:host(.studio-button--icon-only){padding:0;aspect-ratio:1}:host(.studio-button--icon-only.studio-button--sm){width:2rem;padding:0}:host(.studio-button--icon-only.studio-button--md){width:2.5rem;padding:0}:host(.studio-button--icon-only.studio-button--lg){width:3rem;padding:0}:host(.studio-button--loading){cursor:wait;pointer-events:none}:host([disabled]){opacity:.6;cursor:not-allowed!important;background:var(--studio-bg-secondary)!important;color:var(--studio-text-tertiary)!important;border-color:var(--studio-border-primary)!important}:host([disabled]:hover){transform:none!important;background:var(--studio-bg-secondary)!important}:host(:active:not([disabled]):not(.studio-button--loading)){transform:scale(.98)}.studio-button__content{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}.studio-button__spinner{animation:studio-spin 1s linear infinite}@keyframes studio-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "studio-icon", inputs: ["name", "size", "color", "strokeWidth", "absoluteStrokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
759
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;gap:var(--studio-spacing-sm);font-family:var(--studio-font-family);font-weight:var(--studio-font-weight-medium);border:none;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;transition:background-color var(--studio-transition-fast),color var(--studio-transition-fast),border-color var(--studio-transition-fast),box-shadow var(--studio-transition-fast),transform var(--studio-transition-fast)}:host:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}:host(.studio-button--sm){padding:var(--studio-spacing-xs) var(--studio-spacing-sm);font-size:var(--studio-font-size-sm);height:2rem;min-width:2rem}:host(.studio-button--md){padding:var(--studio-spacing-sm) var(--studio-spacing-md);font-size:var(--studio-font-size-base);height:2.5rem;min-width:2.5rem}:host(.studio-button--lg){padding:var(--studio-spacing-md) var(--studio-spacing-lg);font-size:var(--studio-font-size-lg);height:3rem;min-width:3rem}:host(.studio-button--radius-none){border-radius:var(--studio-radius-none)}:host(.studio-button--radius-sm){border-radius:var(--studio-radius-sm)}:host(.studio-button--radius-md){border-radius:var(--studio-radius-md)}:host(.studio-button--radius-lg){border-radius:var(--studio-radius-lg)}:host(.studio-button--radius-xl){border-radius:var(--studio-radius-xl)}:host(.studio-button--radius-full){border-radius:var(--studio-radius-full)}:host(.studio-button--shadow-none){box-shadow:none}:host(.studio-button--shadow-sm){box-shadow:var(--studio-shadow-sm)}:host(.studio-button--shadow-md){box-shadow:var(--studio-shadow-md)}:host(.studio-button--shadow-lg){box-shadow:var(--studio-shadow-lg)}:host(.studio-button--compact.studio-button--sm){padding:var(--studio-spacing-xs) var(--studio-spacing-sm);height:1.75rem;min-width:1.75rem;font-size:.813rem}:host(.studio-button--compact.studio-button--md){padding:var(--studio-spacing-sm) var(--studio-spacing-md);height:2rem;min-width:2rem;font-size:.875rem}:host(.studio-button--compact.studio-button--lg){padding:var(--studio-spacing-sm) var(--studio-spacing-lg);height:2.5rem;min-width:2.5rem;font-size:1rem}:host(.studio-button--solid.studio-button--primary){background:var(--studio-primary);color:#fff}:host(.studio-button--solid.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary-hover)}:host(.studio-button--outline.studio-button--primary){background:transparent;border:1px solid var(--studio-primary);color:var(--studio-primary)}:host(.studio-button--outline.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary);color:#fff}:host(.studio-button--ghost.studio-button--primary){background:transparent;color:var(--studio-primary)}:host(.studio-button--ghost.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary-bg)}:host(.studio-button--solid.studio-button--secondary){background:var(--studio-secondary);color:#fff}:host(.studio-button--solid.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary-hover)}:host(.studio-button--outline.studio-button--secondary){background:transparent;border:1px solid var(--studio-secondary);color:var(--studio-secondary)}:host(.studio-button--outline.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary);color:#fff}:host(.studio-button--ghost.studio-button--secondary){background:transparent;color:var(--studio-secondary)}:host(.studio-button--ghost.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary-bg)}:host(.studio-button--solid.studio-button--success){background:var(--studio-success);color:#fff}:host(.studio-button--solid.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-hover)}:host(.studio-button--outline.studio-button--success){background:transparent;border:1px solid var(--studio-success);color:var(--studio-success)}:host(.studio-button--outline.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success);color:#fff}:host(.studio-button--ghost.studio-button--success){background:transparent;color:var(--studio-success)}:host(.studio-button--ghost.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-bg)}:host(.studio-button--solid.studio-button--error){background:var(--studio-error);color:#fff}:host(.studio-button--solid.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-hover)}:host(.studio-button--outline.studio-button--error){background:transparent;border:1px solid var(--studio-error);color:var(--studio-error)}:host(.studio-button--outline.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error);color:#fff}:host(.studio-button--ghost.studio-button--error){background:transparent;color:var(--studio-error)}:host(.studio-button--ghost.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-bg)}:host(.studio-button--solid.studio-button--warning){background:var(--studio-warning);color:#fff}:host(.studio-button--solid.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-hover)}:host(.studio-button--outline.studio-button--warning){background:transparent;border:1px solid var(--studio-warning);color:var(--studio-warning)}:host(.studio-button--outline.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning);color:#fff}:host(.studio-button--ghost.studio-button--warning){background:transparent;color:var(--studio-warning)}:host(.studio-button--ghost.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-bg)}:host(.studio-button--full){width:100%}:host(.studio-button--icon-only){padding:0;aspect-ratio:1}:host(.studio-button--icon-only.studio-button--sm){width:2rem;padding:0}:host(.studio-button--icon-only.studio-button--md){width:2.5rem;padding:0}:host(.studio-button--icon-only.studio-button--lg){width:3rem;padding:0}:host(.studio-button--loading){cursor:wait;pointer-events:none}:host([disabled]){opacity:.6;cursor:not-allowed!important;background:var(--studio-bg-secondary)!important;color:var(--studio-text-tertiary)!important;border-color:var(--studio-border-primary)!important}:host([disabled]:hover){transform:none!important;background:var(--studio-bg-secondary)!important}:host(:active:not([disabled]):not(.studio-button--loading)){transform:scale(.98)}.studio-button__content{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}.studio-button__spinner{animation:studio-spin 1s linear infinite}@keyframes studio-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.studio-button__badge{position:absolute;top:-.375rem;right:-.375rem;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 .25rem;font-size:.625rem;font-weight:var(--studio-font-weight-semibold);line-height:1;color:#fff;border-radius:var(--studio-radius-full);box-shadow:0 0 0 2px var(--studio-bg-primary)}.studio-button__badge--primary{background:var(--studio-primary)}.studio-button__badge--error{background:var(--studio-error)}.studio-button__badge--warning{background:var(--studio-warning)}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "studio-icon", inputs: ["name", "size", "color", "strokeWidth", "absoluteStrokeWidth", "showFallback", "fallbackIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
434
760
  }
435
761
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ButtonComponent, decorators: [{
436
762
  type: Component,
437
763
  args: [{ selector: 'studio-button', standalone: true, imports: [IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
438
764
  '[class]': 'hostClasses()',
439
765
  '[attr.disabled]': 'disabled() || loading() ? "" : null',
440
- '[attr.type]': 'type()',
766
+ '[attr.type]': 'href() ? null : type()',
767
+ '[attr.href]': 'href()',
768
+ '[attr.target]': 'href() ? target() : null',
769
+ '[attr.rel]': 'href() && target() === "_blank" ? "noopener noreferrer" : null',
770
+ '[attr.aria-label]': 'ariaLabel()',
441
771
  '(click)': 'handleClick($event)'
442
772
  }, template: `
443
773
  <span class="studio-button__content">
@@ -461,23 +791,222 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
461
791
  <studio-icon [name]="icon()!" [size]="iconSize()" />
462
792
  }
463
793
  @if (loading() && iconPosition() === 'only') {
464
- <!-- Empty, spinner already shown -->
465
794
  } @else if (loading()) {
466
- <span>Loading...</span>
795
+ <span>{{ loadingText() }}</span>
796
+ }
797
+ @if (badge() !== undefined && badge() !== null && badge() !== '') {
798
+ <span class="studio-button__badge studio-button__badge--{{ badgeColor() }}">
799
+ {{ badge() }}
800
+ </span>
467
801
  }
468
802
  </span>
469
- `, styles: [":host{display:inline-flex;align-items:center;justify-content:center;gap:var(--studio-spacing-sm);font-family:var(--studio-font-family);font-weight:var(--studio-font-weight-medium);border:none;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;border-radius:var(--studio-radius-md);transition:background-color var(--studio-transition-fast),color var(--studio-transition-fast),border-color var(--studio-transition-fast),box-shadow var(--studio-transition-fast),transform var(--studio-transition-fast)}:host:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}:host(.studio-button--sm){padding:var(--studio-spacing-xs) var(--studio-spacing-sm);font-size:var(--studio-font-size-sm);height:2rem;min-width:2rem}:host(.studio-button--md){padding:var(--studio-spacing-sm) var(--studio-spacing-md);font-size:var(--studio-font-size-base);height:2.5rem;min-width:2.5rem}:host(.studio-button--lg){padding:var(--studio-spacing-md) var(--studio-spacing-lg);font-size:var(--studio-font-size-lg);height:3rem;min-width:3rem}:host(.studio-button--solid.studio-button--primary){background:var(--studio-primary);color:#fff}:host(.studio-button--solid.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary-hover)}:host(.studio-button--outline.studio-button--primary){background:transparent;border:1px solid var(--studio-primary);color:var(--studio-primary)}:host(.studio-button--outline.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary);color:#fff}:host(.studio-button--ghost.studio-button--primary){background:transparent;color:var(--studio-primary)}:host(.studio-button--ghost.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:#3b82f61a}:host(.studio-button--solid.studio-button--secondary){background:var(--studio-secondary);color:#fff}:host(.studio-button--solid.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary-hover)}:host(.studio-button--outline.studio-button--secondary){background:transparent;border:1px solid var(--studio-secondary);color:var(--studio-secondary)}:host(.studio-button--outline.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary);color:#fff}:host(.studio-button--ghost.studio-button--secondary){background:transparent;color:var(--studio-secondary)}:host(.studio-button--ghost.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:#8b5cf61a}:host(.studio-button--solid.studio-button--success){background:var(--studio-success);color:#fff}:host(.studio-button--solid.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-hover)}:host(.studio-button--outline.studio-button--success){background:transparent;border:1px solid var(--studio-success);color:var(--studio-success)}:host(.studio-button--outline.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success);color:#fff}:host(.studio-button--ghost.studio-button--success){background:transparent;color:var(--studio-success)}:host(.studio-button--ghost.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-bg)}:host(.studio-button--solid.studio-button--error){background:var(--studio-error);color:#fff}:host(.studio-button--solid.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-hover)}:host(.studio-button--outline.studio-button--error){background:transparent;border:1px solid var(--studio-error);color:var(--studio-error)}:host(.studio-button--outline.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error);color:#fff}:host(.studio-button--ghost.studio-button--error){background:transparent;color:var(--studio-error)}:host(.studio-button--ghost.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-bg)}:host(.studio-button--solid.studio-button--warning){background:var(--studio-warning);color:#fff}:host(.studio-button--solid.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-hover)}:host(.studio-button--outline.studio-button--warning){background:transparent;border:1px solid var(--studio-warning);color:var(--studio-warning)}:host(.studio-button--outline.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning);color:#fff}:host(.studio-button--ghost.studio-button--warning){background:transparent;color:var(--studio-warning)}:host(.studio-button--ghost.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-bg)}:host(.studio-button--full){width:100%}:host(.studio-button--icon-only){padding:0;aspect-ratio:1}:host(.studio-button--icon-only.studio-button--sm){width:2rem;padding:0}:host(.studio-button--icon-only.studio-button--md){width:2.5rem;padding:0}:host(.studio-button--icon-only.studio-button--lg){width:3rem;padding:0}:host(.studio-button--loading){cursor:wait;pointer-events:none}:host([disabled]){opacity:.6;cursor:not-allowed!important;background:var(--studio-bg-secondary)!important;color:var(--studio-text-tertiary)!important;border-color:var(--studio-border-primary)!important}:host([disabled]:hover){transform:none!important;background:var(--studio-bg-secondary)!important}:host(:active:not([disabled]):not(.studio-button--loading)){transform:scale(.98)}.studio-button__content{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}.studio-button__spinner{animation:studio-spin 1s linear infinite}@keyframes studio-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
470
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPosition", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
803
+ `, styles: [":host{display:inline-flex;align-items:center;justify-content:center;gap:var(--studio-spacing-sm);font-family:var(--studio-font-family);font-weight:var(--studio-font-weight-medium);border:none;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;transition:background-color var(--studio-transition-fast),color var(--studio-transition-fast),border-color var(--studio-transition-fast),box-shadow var(--studio-transition-fast),transform var(--studio-transition-fast)}:host:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}:host(.studio-button--sm){padding:var(--studio-spacing-xs) var(--studio-spacing-sm);font-size:var(--studio-font-size-sm);height:2rem;min-width:2rem}:host(.studio-button--md){padding:var(--studio-spacing-sm) var(--studio-spacing-md);font-size:var(--studio-font-size-base);height:2.5rem;min-width:2.5rem}:host(.studio-button--lg){padding:var(--studio-spacing-md) var(--studio-spacing-lg);font-size:var(--studio-font-size-lg);height:3rem;min-width:3rem}:host(.studio-button--radius-none){border-radius:var(--studio-radius-none)}:host(.studio-button--radius-sm){border-radius:var(--studio-radius-sm)}:host(.studio-button--radius-md){border-radius:var(--studio-radius-md)}:host(.studio-button--radius-lg){border-radius:var(--studio-radius-lg)}:host(.studio-button--radius-xl){border-radius:var(--studio-radius-xl)}:host(.studio-button--radius-full){border-radius:var(--studio-radius-full)}:host(.studio-button--shadow-none){box-shadow:none}:host(.studio-button--shadow-sm){box-shadow:var(--studio-shadow-sm)}:host(.studio-button--shadow-md){box-shadow:var(--studio-shadow-md)}:host(.studio-button--shadow-lg){box-shadow:var(--studio-shadow-lg)}:host(.studio-button--compact.studio-button--sm){padding:var(--studio-spacing-xs) var(--studio-spacing-sm);height:1.75rem;min-width:1.75rem;font-size:.813rem}:host(.studio-button--compact.studio-button--md){padding:var(--studio-spacing-sm) var(--studio-spacing-md);height:2rem;min-width:2rem;font-size:.875rem}:host(.studio-button--compact.studio-button--lg){padding:var(--studio-spacing-sm) var(--studio-spacing-lg);height:2.5rem;min-width:2.5rem;font-size:1rem}:host(.studio-button--solid.studio-button--primary){background:var(--studio-primary);color:#fff}:host(.studio-button--solid.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary-hover)}:host(.studio-button--outline.studio-button--primary){background:transparent;border:1px solid var(--studio-primary);color:var(--studio-primary)}:host(.studio-button--outline.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary);color:#fff}:host(.studio-button--ghost.studio-button--primary){background:transparent;color:var(--studio-primary)}:host(.studio-button--ghost.studio-button--primary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-primary-bg)}:host(.studio-button--solid.studio-button--secondary){background:var(--studio-secondary);color:#fff}:host(.studio-button--solid.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary-hover)}:host(.studio-button--outline.studio-button--secondary){background:transparent;border:1px solid var(--studio-secondary);color:var(--studio-secondary)}:host(.studio-button--outline.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary);color:#fff}:host(.studio-button--ghost.studio-button--secondary){background:transparent;color:var(--studio-secondary)}:host(.studio-button--ghost.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-secondary-bg)}:host(.studio-button--solid.studio-button--success){background:var(--studio-success);color:#fff}:host(.studio-button--solid.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-hover)}:host(.studio-button--outline.studio-button--success){background:transparent;border:1px solid var(--studio-success);color:var(--studio-success)}:host(.studio-button--outline.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success);color:#fff}:host(.studio-button--ghost.studio-button--success){background:transparent;color:var(--studio-success)}:host(.studio-button--ghost.studio-button--success:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-success-bg)}:host(.studio-button--solid.studio-button--error){background:var(--studio-error);color:#fff}:host(.studio-button--solid.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-hover)}:host(.studio-button--outline.studio-button--error){background:transparent;border:1px solid var(--studio-error);color:var(--studio-error)}:host(.studio-button--outline.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error);color:#fff}:host(.studio-button--ghost.studio-button--error){background:transparent;color:var(--studio-error)}:host(.studio-button--ghost.studio-button--error:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-error-bg)}:host(.studio-button--solid.studio-button--warning){background:var(--studio-warning);color:#fff}:host(.studio-button--solid.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-hover)}:host(.studio-button--outline.studio-button--warning){background:transparent;border:1px solid var(--studio-warning);color:var(--studio-warning)}:host(.studio-button--outline.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning);color:#fff}:host(.studio-button--ghost.studio-button--warning){background:transparent;color:var(--studio-warning)}:host(.studio-button--ghost.studio-button--warning:hover:not(:disabled):not(.studio-button--loading)){background:var(--studio-warning-bg)}:host(.studio-button--full){width:100%}:host(.studio-button--icon-only){padding:0;aspect-ratio:1}:host(.studio-button--icon-only.studio-button--sm){width:2rem;padding:0}:host(.studio-button--icon-only.studio-button--md){width:2.5rem;padding:0}:host(.studio-button--icon-only.studio-button--lg){width:3rem;padding:0}:host(.studio-button--loading){cursor:wait;pointer-events:none}:host([disabled]){opacity:.6;cursor:not-allowed!important;background:var(--studio-bg-secondary)!important;color:var(--studio-text-tertiary)!important;border-color:var(--studio-border-primary)!important}:host([disabled]:hover){transform:none!important;background:var(--studio-bg-secondary)!important}:host(:active:not([disabled]):not(.studio-button--loading)){transform:scale(.98)}.studio-button__content{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}.studio-button__spinner{animation:studio-spin 1s linear infinite}@keyframes studio-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.studio-button__badge{position:absolute;top:-.375rem;right:-.375rem;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 .25rem;font-size:.625rem;font-weight:var(--studio-font-weight-semibold);line-height:1;color:#fff;border-radius:var(--studio-radius-full);box-shadow:0 0 0 2px var(--studio-bg-primary)}.studio-button__badge--primary{background:var(--studio-primary)}.studio-button__badge--error{background:var(--studio-error)}.studio-button__badge--warning{background:var(--studio-warning)}\n"] }]
804
+ }], propDecorators: { variantInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], sizeInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], colorInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], radiusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], shadowInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "shadow", required: false }] }], compactInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPosition", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], badgeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeColor", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
471
805
 
472
806
  /**
473
807
  * Button component
474
808
  */
475
809
 
810
+ /**
811
+ * Toggle switch component with customizable size and color
812
+ *
813
+ * @example
814
+ * <studio-switch [(checked)]="isEnabled" label="Enable feature" />
815
+ */
816
+ class SwitchComponent {
817
+ configService = inject(StudioConfigService);
818
+ switchDefaults = computed(() => this.configService.config().components?.switch, ...(ngDevMode ? [{ debugName: "switchDefaults" }] : []));
819
+ checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
820
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
821
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : []));
822
+ labelPosition = input('right', ...(ngDevMode ? [{ debugName: "labelPosition" }] : []));
823
+ showIcons = input(false, ...(ngDevMode ? [{ debugName: "showIcons" }] : []));
824
+ ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : []));
825
+ sizeInput = input(undefined, ...(ngDevMode ? [{ debugName: "sizeInput", alias: 'size' }] : [{ alias: 'size' }]));
826
+ colorInput = input(undefined, ...(ngDevMode ? [{ debugName: "colorInput", alias: 'color' }] : [{ alias: 'color' }]));
827
+ size = withConfigDefault(this.sizeInput, computed(() => this.switchDefaults()?.size), 'md');
828
+ color = withConfigDefault(this.colorInput, computed(() => this.switchDefaults()?.color), 'primary');
829
+ checkedChange = output();
830
+ iconSize = computed(() => {
831
+ const sizeMap = { sm: 10, md: 12, lg: 14 };
832
+ return sizeMap[this.size()];
833
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
834
+ hostClasses = computed(() => classNames('studio-switch', `studio-switch--${this.size()}`, `studio-switch--${this.color()}`, this.checked() && 'studio-switch--checked', this.disabled() && 'studio-switch--disabled'), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
835
+ handleClick() {
836
+ if (this.disabled())
837
+ return;
838
+ this.toggle();
839
+ }
840
+ handleKeydown(event) {
841
+ if (this.disabled())
842
+ return;
843
+ event.preventDefault();
844
+ this.toggle();
845
+ }
846
+ toggle() {
847
+ const newValue = !this.checked();
848
+ this.checked.set(newValue);
849
+ this.checkedChange.emit(newValue);
850
+ }
851
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SwitchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
852
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: SwitchComponent, isStandalone: true, selector: "studio-switch", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, showIcons: { classPropertyName: "showIcons", publicName: "showIcons", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, sizeInput: { classPropertyName: "sizeInput", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, colorInput: { classPropertyName: "colorInput", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", checkedChange: "checkedChange" }, host: { listeners: { "click": "handleClick()", "keydown.space": "handleKeydown($event)", "keydown.enter": "handleKeydown($event)" }, properties: { "class": "hostClasses()", "attr.role": "\"switch\"", "attr.aria-checked": "checked()", "attr.aria-disabled": "disabled() ? \"true\" : null", "attr.aria-label": "ariaLabel() || label()", "tabindex": "disabled() ? -1 : 0" } }, ngImport: i0, template: `
853
+ <span class="studio-switch__container">
854
+ @if (label() && labelPosition() === 'left') {
855
+ <span class="studio-switch__label studio-switch__label--left">
856
+ {{ label() }}
857
+ </span>
858
+ }
859
+
860
+ <span class="studio-switch__track">
861
+ <span class="studio-switch__thumb">
862
+ @if (showIcons()) {
863
+ @if (checked()) {
864
+ <studio-icon name="check" [size]="iconSize()" color="inherit" />
865
+ } @else {
866
+ <studio-icon name="x" [size]="iconSize()" color="inherit" />
867
+ }
868
+ }
869
+ </span>
870
+ </span>
871
+
872
+ @if (label() && labelPosition() === 'right') {
873
+ <span class="studio-switch__label studio-switch__label--right">
874
+ {{ label() }}
875
+ </span>
876
+ }
877
+ </span>
878
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;transition:opacity var(--studio-transition-fast)}:host(:focus-visible) .studio-switch__track{outline:2px solid var(--studio-primary);outline-offset:2px}:host(.studio-switch--disabled){cursor:not-allowed;opacity:.5}.studio-switch__container{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}.studio-switch__label{font-size:var(--studio-font-size-sm);color:var(--studio-text-primary);font-weight:var(--studio-font-weight-medium)}.studio-switch__track{position:relative;display:inline-flex;align-items:center;border-radius:var(--studio-radius-full);background:var(--studio-bg-tertiary);transition:background-color var(--studio-transition-base);flex-shrink:0}.studio-switch__thumb{position:absolute;display:inline-flex;align-items:center;justify-content:center;background:#fff;border-radius:var(--studio-radius-full);box-shadow:var(--studio-shadow-sm);transition:transform var(--studio-transition-base),background-color var(--studio-transition-base);color:var(--studio-text-tertiary)}:host(.studio-switch--sm) .studio-switch__track{width:2rem;height:1.25rem;padding:.125rem}:host(.studio-switch--sm) .studio-switch__thumb{width:1rem;height:1rem;left:.125rem}:host(.studio-switch--md) .studio-switch__track{width:2.5rem;height:1.5rem;padding:.125rem}:host(.studio-switch--md) .studio-switch__thumb{width:1.25rem;height:1.25rem;left:.125rem}:host(.studio-switch--lg) .studio-switch__track{width:3rem;height:1.75rem;padding:.125rem}:host(.studio-switch--lg) .studio-switch__thumb{width:1.5rem;height:1.5rem;left:.125rem}:host(.studio-switch--checked.studio-switch--primary) .studio-switch__track{background:var(--studio-primary)}:host(.studio-switch--checked.studio-switch--primary) .studio-switch__thumb{color:var(--studio-primary)}:host(.studio-switch--checked.studio-switch--sm.studio-switch--primary) .studio-switch__thumb{transform:translate(.75rem)}:host(.studio-switch--checked.studio-switch--md.studio-switch--primary) .studio-switch__thumb{transform:translate(1rem)}:host(.studio-switch--checked.studio-switch--lg.studio-switch--primary) .studio-switch__thumb{transform:translate(1.25rem)}:host(.studio-switch--checked.studio-switch--secondary) .studio-switch__track{background:var(--studio-secondary)}:host(.studio-switch--checked.studio-switch--secondary) .studio-switch__thumb{color:var(--studio-secondary)}:host(.studio-switch--checked.studio-switch--sm.studio-switch--secondary) .studio-switch__thumb{transform:translate(.75rem)}:host(.studio-switch--checked.studio-switch--md.studio-switch--secondary) .studio-switch__thumb{transform:translate(1rem)}:host(.studio-switch--checked.studio-switch--lg.studio-switch--secondary) .studio-switch__thumb{transform:translate(1.25rem)}:host(.studio-switch--checked.studio-switch--success) .studio-switch__track{background:var(--studio-success)}:host(.studio-switch--checked.studio-switch--success) .studio-switch__thumb{color:var(--studio-success)}:host(.studio-switch--checked.studio-switch--sm.studio-switch--success) .studio-switch__thumb{transform:translate(.75rem)}:host(.studio-switch--checked.studio-switch--md.studio-switch--success) .studio-switch__thumb{transform:translate(1rem)}:host(.studio-switch--checked.studio-switch--lg.studio-switch--success) .studio-switch__thumb{transform:translate(1.25rem)}:host(:not(.studio-switch--disabled):hover) .studio-switch__track{opacity:.9}:host(.studio-switch--disabled) .studio-switch__track{background:var(--studio-bg-secondary)!important}:host(.studio-switch--disabled) .studio-switch__thumb{box-shadow:none}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "studio-icon", inputs: ["name", "size", "color", "strokeWidth", "absoluteStrokeWidth", "showFallback", "fallbackIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
879
+ }
880
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SwitchComponent, decorators: [{
881
+ type: Component,
882
+ args: [{ selector: 'studio-switch', standalone: true, imports: [IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
883
+ '[class]': 'hostClasses()',
884
+ '[attr.role]': '"switch"',
885
+ '[attr.aria-checked]': 'checked()',
886
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
887
+ '[attr.aria-label]': 'ariaLabel() || label()',
888
+ '(click)': 'handleClick()',
889
+ '(keydown.space)': 'handleKeydown($event)',
890
+ '(keydown.enter)': 'handleKeydown($event)',
891
+ '[tabindex]': 'disabled() ? -1 : 0'
892
+ }, template: `
893
+ <span class="studio-switch__container">
894
+ @if (label() && labelPosition() === 'left') {
895
+ <span class="studio-switch__label studio-switch__label--left">
896
+ {{ label() }}
897
+ </span>
898
+ }
899
+
900
+ <span class="studio-switch__track">
901
+ <span class="studio-switch__thumb">
902
+ @if (showIcons()) {
903
+ @if (checked()) {
904
+ <studio-icon name="check" [size]="iconSize()" color="inherit" />
905
+ } @else {
906
+ <studio-icon name="x" [size]="iconSize()" color="inherit" />
907
+ }
908
+ }
909
+ </span>
910
+ </span>
911
+
912
+ @if (label() && labelPosition() === 'right') {
913
+ <span class="studio-switch__label studio-switch__label--right">
914
+ {{ label() }}
915
+ </span>
916
+ }
917
+ </span>
918
+ `, styles: [":host{display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;transition:opacity var(--studio-transition-fast)}:host(:focus-visible) .studio-switch__track{outline:2px solid var(--studio-primary);outline-offset:2px}:host(.studio-switch--disabled){cursor:not-allowed;opacity:.5}.studio-switch__container{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}.studio-switch__label{font-size:var(--studio-font-size-sm);color:var(--studio-text-primary);font-weight:var(--studio-font-weight-medium)}.studio-switch__track{position:relative;display:inline-flex;align-items:center;border-radius:var(--studio-radius-full);background:var(--studio-bg-tertiary);transition:background-color var(--studio-transition-base);flex-shrink:0}.studio-switch__thumb{position:absolute;display:inline-flex;align-items:center;justify-content:center;background:#fff;border-radius:var(--studio-radius-full);box-shadow:var(--studio-shadow-sm);transition:transform var(--studio-transition-base),background-color var(--studio-transition-base);color:var(--studio-text-tertiary)}:host(.studio-switch--sm) .studio-switch__track{width:2rem;height:1.25rem;padding:.125rem}:host(.studio-switch--sm) .studio-switch__thumb{width:1rem;height:1rem;left:.125rem}:host(.studio-switch--md) .studio-switch__track{width:2.5rem;height:1.5rem;padding:.125rem}:host(.studio-switch--md) .studio-switch__thumb{width:1.25rem;height:1.25rem;left:.125rem}:host(.studio-switch--lg) .studio-switch__track{width:3rem;height:1.75rem;padding:.125rem}:host(.studio-switch--lg) .studio-switch__thumb{width:1.5rem;height:1.5rem;left:.125rem}:host(.studio-switch--checked.studio-switch--primary) .studio-switch__track{background:var(--studio-primary)}:host(.studio-switch--checked.studio-switch--primary) .studio-switch__thumb{color:var(--studio-primary)}:host(.studio-switch--checked.studio-switch--sm.studio-switch--primary) .studio-switch__thumb{transform:translate(.75rem)}:host(.studio-switch--checked.studio-switch--md.studio-switch--primary) .studio-switch__thumb{transform:translate(1rem)}:host(.studio-switch--checked.studio-switch--lg.studio-switch--primary) .studio-switch__thumb{transform:translate(1.25rem)}:host(.studio-switch--checked.studio-switch--secondary) .studio-switch__track{background:var(--studio-secondary)}:host(.studio-switch--checked.studio-switch--secondary) .studio-switch__thumb{color:var(--studio-secondary)}:host(.studio-switch--checked.studio-switch--sm.studio-switch--secondary) .studio-switch__thumb{transform:translate(.75rem)}:host(.studio-switch--checked.studio-switch--md.studio-switch--secondary) .studio-switch__thumb{transform:translate(1rem)}:host(.studio-switch--checked.studio-switch--lg.studio-switch--secondary) .studio-switch__thumb{transform:translate(1.25rem)}:host(.studio-switch--checked.studio-switch--success) .studio-switch__track{background:var(--studio-success)}:host(.studio-switch--checked.studio-switch--success) .studio-switch__thumb{color:var(--studio-success)}:host(.studio-switch--checked.studio-switch--sm.studio-switch--success) .studio-switch__thumb{transform:translate(.75rem)}:host(.studio-switch--checked.studio-switch--md.studio-switch--success) .studio-switch__thumb{transform:translate(1rem)}:host(.studio-switch--checked.studio-switch--lg.studio-switch--success) .studio-switch__thumb{transform:translate(1.25rem)}:host(:not(.studio-switch--disabled):hover) .studio-switch__track{opacity:.9}:host(.studio-switch--disabled) .studio-switch__track{background:var(--studio-bg-secondary)!important}:host(.studio-switch--disabled) .studio-switch__thumb{box-shadow:none}\n"] }]
919
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], showIcons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcons", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], sizeInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], colorInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], checkedChange: [{ type: i0.Output, args: ["checkedChange"] }] } });
920
+
921
+ /**
922
+ * Switch component
923
+ */
924
+
476
925
  /**
477
926
  * Primitives (Atoms)
478
927
  * Basic building blocks
479
928
  */
480
929
 
930
+ /**
931
+ * Theme toggle switch with sun/moon icons
932
+ *
933
+ * @example
934
+ * <studio-theme-switch />
935
+ * <studio-theme-switch [showLabel]="true" />
936
+ */
937
+ class ThemeSwitchComponent {
938
+ configService = inject(StudioConfigService);
939
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
940
+ showLabel = input(false, ...(ngDevMode ? [{ debugName: "showLabel" }] : []));
941
+ iconOnly = input(true, ...(ngDevMode ? [{ debugName: "iconOnly" }] : []));
942
+ isDark = computed(() => this.configService.themeMode() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : []));
943
+ iconSize = computed(() => {
944
+ const sizeMap = { sm: 16, md: 20, lg: 24 };
945
+ return sizeMap[this.size()];
946
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
947
+ toggleTheme() {
948
+ this.configService.toggleTheme();
949
+ }
950
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ThemeSwitchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
951
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: ThemeSwitchComponent, isStandalone: true, selector: "studio-theme-switch", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
952
+ <div class="studio-theme-switch">
953
+ @if (iconOnly()) {
954
+ <button
955
+ type="button"
956
+ class="studio-theme-switch__button studio-theme-switch__button--{{ size() }}"
957
+ [attr.aria-label]="isDark() ? 'Switch to light mode' : 'Switch to dark mode'"
958
+ (click)="toggleTheme()">
959
+ @if (isDark()) {
960
+ <studio-icon name="moon" [size]="iconSize()" color="inherit" />
961
+ } @else {
962
+ <studio-icon name="sun" [size]="iconSize()" color="inherit" />
963
+ }
964
+ </button>
965
+ } @else {
966
+ <studio-switch
967
+ [checked]="isDark()"
968
+ (checkedChange)="toggleTheme()"
969
+ [size]="size()"
970
+ [label]="showLabel() ? (isDark() ? 'Dark' : 'Light') : undefined"
971
+ labelPosition="left">
972
+ </studio-switch>
973
+ }
974
+ </div>
975
+ `, isInline: true, styles: [".studio-theme-switch{display:inline-flex;align-items:center}.studio-theme-switch__button{display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;color:var(--studio-text-secondary);cursor:pointer;border-radius:var(--studio-radius-md);transition:background-color var(--studio-transition-fast),color var(--studio-transition-fast);padding:var(--studio-spacing-xs)}.studio-theme-switch__button:hover{background:var(--studio-bg-secondary);color:var(--studio-text-primary)}.studio-theme-switch__button:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}.studio-theme-switch__button--sm{width:2rem;height:2rem}.studio-theme-switch__button--md{width:2.5rem;height:2.5rem}.studio-theme-switch__button--lg{width:3rem;height:3rem}\n"], dependencies: [{ kind: "component", type: SwitchComponent, selector: "studio-switch", inputs: ["checked", "disabled", "label", "labelPosition", "showIcons", "ariaLabel", "size", "color"], outputs: ["checkedChange"] }, { kind: "component", type: IconComponent, selector: "studio-icon", inputs: ["name", "size", "color", "strokeWidth", "absoluteStrokeWidth", "showFallback", "fallbackIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
976
+ }
977
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ThemeSwitchComponent, decorators: [{
978
+ type: Component,
979
+ args: [{ selector: 'studio-theme-switch', standalone: true, imports: [SwitchComponent, IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
980
+ <div class="studio-theme-switch">
981
+ @if (iconOnly()) {
982
+ <button
983
+ type="button"
984
+ class="studio-theme-switch__button studio-theme-switch__button--{{ size() }}"
985
+ [attr.aria-label]="isDark() ? 'Switch to light mode' : 'Switch to dark mode'"
986
+ (click)="toggleTheme()">
987
+ @if (isDark()) {
988
+ <studio-icon name="moon" [size]="iconSize()" color="inherit" />
989
+ } @else {
990
+ <studio-icon name="sun" [size]="iconSize()" color="inherit" />
991
+ }
992
+ </button>
993
+ } @else {
994
+ <studio-switch
995
+ [checked]="isDark()"
996
+ (checkedChange)="toggleTheme()"
997
+ [size]="size()"
998
+ [label]="showLabel() ? (isDark() ? 'Dark' : 'Light') : undefined"
999
+ labelPosition="left">
1000
+ </studio-switch>
1001
+ }
1002
+ </div>
1003
+ `, styles: [".studio-theme-switch{display:inline-flex;align-items:center}.studio-theme-switch__button{display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;color:var(--studio-text-secondary);cursor:pointer;border-radius:var(--studio-radius-md);transition:background-color var(--studio-transition-fast),color var(--studio-transition-fast);padding:var(--studio-spacing-xs)}.studio-theme-switch__button:hover{background:var(--studio-bg-secondary);color:var(--studio-text-primary)}.studio-theme-switch__button:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}.studio-theme-switch__button--sm{width:2rem;height:2rem}.studio-theme-switch__button--md{width:2.5rem;height:2.5rem}.studio-theme-switch__button--lg{width:3rem;height:3rem}\n"] }]
1004
+ }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }] } });
1005
+
1006
+ /**
1007
+ * Theme switch component
1008
+ */
1009
+
481
1010
  /**
482
1011
  * Composites (Molecules + Organisms)
483
1012
  * Complex components built from primitives
@@ -488,11 +1017,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
488
1017
  * Domain-specific components for educational platforms
489
1018
  */
490
1019
 
491
- /**
492
- * Utilities
493
- * Helper functions, directives, pipes
494
- */
495
-
496
1020
  /**
497
1021
  * Public API Surface of @eduboxpro/studio
498
1022
  */
@@ -504,5 +1028,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
504
1028
  * Generated bundle index. Do not edit.
505
1029
  */
506
1030
 
507
- export { ButtonComponent, IconComponent, STUDIO_CONFIG, StudioConfigService, provideStudioConfig, provideStudioIcons };
1031
+ export { BadgeComponent, ButtonComponent, IconComponent, STUDIO_CONFIG, StudioConfigService, SwitchComponent, ThemeSwitchComponent, classNames, isSafeUrl, provideStudioConfig, provideStudioIcons, sanitizeUrl, withConfigDefault };
508
1032
  //# sourceMappingURL=eduboxpro-studio.mjs.map