@eduboxpro/studio 0.1.0 → 0.1.2

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,6 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, signal, effect, Injectable, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, input, output, computed, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { InjectionToken, inject, signal, effect, Injectable, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, input, computed, ChangeDetectionStrategy, Component, output } from '@angular/core';
3
3
  import { DOCUMENT } from '@angular/common';
4
+ import * as i1 from 'lucide-angular';
5
+ import { icons, LucideAngularModule, LucideIconProvider, LUCIDE_ICONS, 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';
4
6
 
5
7
  /**
6
8
  * Injection token for Studio configuration
@@ -51,28 +53,105 @@ class StudioConfigService {
51
53
  if (!theme)
52
54
  return;
53
55
  const root = this.document.documentElement;
56
+ // Apply colors
54
57
  if (theme.colors) {
55
- this.applyTokens(root, 'studio', theme.colors);
58
+ this.applyColorTokens(root, theme.colors);
56
59
  }
60
+ // Apply typography
61
+ if (theme.typography) {
62
+ if (theme.typography.fontFamily) {
63
+ root.style.setProperty('--studio-font-family', theme.typography.fontFamily);
64
+ }
65
+ if (theme.typography.fontMono) {
66
+ root.style.setProperty('--studio-font-mono', theme.typography.fontMono);
67
+ }
68
+ if (theme.typography.fontSize) {
69
+ this.applyTokens(root, 'studio-font-size', theme.typography.fontSize);
70
+ }
71
+ if (theme.typography.fontWeight) {
72
+ this.applyTokens(root, 'studio-font-weight', theme.typography.fontWeight);
73
+ }
74
+ if (theme.typography.lineHeight) {
75
+ this.applyTokens(root, 'studio-line-height', theme.typography.lineHeight);
76
+ }
77
+ }
78
+ // Apply spacing
57
79
  if (theme.spacing) {
58
80
  this.applyTokens(root, 'studio-spacing', theme.spacing);
59
81
  }
82
+ // Apply border radius
60
83
  if (theme.borderRadius) {
61
84
  this.applyTokens(root, 'studio-radius', theme.borderRadius);
62
85
  }
86
+ // Apply shadows
63
87
  if (theme.shadows) {
64
88
  this.applyTokens(root, 'studio-shadow', theme.shadows);
65
89
  }
66
- if (theme.typography?.fontFamily) {
67
- root.style.setProperty('--studio-font-family', theme.typography.fontFamily);
90
+ // Apply transitions
91
+ if (theme.transitions) {
92
+ this.applyTokens(root, 'studio-transition', theme.transitions);
93
+ }
94
+ // Apply z-index
95
+ if (theme.zIndex) {
96
+ this.applyZIndexTokens(root, theme.zIndex);
97
+ }
98
+ }
99
+ applyColorTokens(root, colors) {
100
+ // Brand colors with hover/active states
101
+ const brandColors = ['primary', 'secondary', 'success', 'error', 'warning', 'info'];
102
+ brandColors.forEach(colorName => {
103
+ const colorValue = colors[colorName];
104
+ if (!colorValue)
105
+ return;
106
+ if (typeof colorValue === 'string') {
107
+ root.style.setProperty(`--studio-${colorName}`, colorValue);
108
+ }
109
+ else {
110
+ const config = colorValue;
111
+ if (config.base) {
112
+ root.style.setProperty(`--studio-${colorName}`, config.base);
113
+ }
114
+ if (config.hover) {
115
+ root.style.setProperty(`--studio-${colorName}-hover`, config.hover);
116
+ }
117
+ if (config.active) {
118
+ root.style.setProperty(`--studio-${colorName}-active`, config.active);
119
+ }
120
+ if (config.bg) {
121
+ root.style.setProperty(`--studio-${colorName}-bg`, config.bg);
122
+ }
123
+ }
124
+ });
125
+ // Background colors
126
+ if (colors.bg) {
127
+ this.applyTokens(root, 'studio-bg', colors.bg);
68
128
  }
69
- if (theme.typography?.fontSize) {
70
- this.applyTokens(root, 'studio-font-size', theme.typography.fontSize);
129
+ // Text colors
130
+ if (colors.text) {
131
+ this.applyTokens(root, 'studio-text', colors.text);
71
132
  }
72
- if (theme.typography?.fontWeight) {
73
- this.applyTokens(root, 'studio-font-weight', theme.typography.fontWeight);
133
+ // Border colors
134
+ if (colors.border) {
135
+ this.applyTokens(root, 'studio-border', colors.border);
74
136
  }
75
137
  }
138
+ applyZIndexTokens(root, zIndex) {
139
+ const zIndexMap = {
140
+ dropdown: 'dropdown',
141
+ sticky: 'sticky',
142
+ fixed: 'fixed',
143
+ modalBackdrop: 'modal-backdrop',
144
+ modal: 'modal',
145
+ popover: 'popover',
146
+ tooltip: 'tooltip'
147
+ };
148
+ Object.entries(zIndex).forEach(([key, value]) => {
149
+ if (value !== undefined && value !== null) {
150
+ const cssKey = zIndexMap[key] || key;
151
+ root.style.setProperty(`--studio-z-${cssKey}`, String(value));
152
+ }
153
+ });
154
+ }
76
155
  applyTokens(root, prefix, tokens) {
77
156
  Object.entries(tokens).forEach(([key, value]) => {
78
157
  if (value !== undefined && value !== null) {
@@ -133,53 +212,606 @@ function provideStudioConfig(config = {}) {
133
212
  * @module config
134
213
  */
135
214
 
215
+ class IconComponent {
216
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
217
+ size = input(24, ...(ngDevMode ? [{ debugName: "size" }] : []));
218
+ color = input('inherit', ...(ngDevMode ? [{ debugName: "color" }] : []));
219
+ strokeWidth = input(2, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : []));
220
+ absoluteStrokeWidth = input(false, ...(ngDevMode ? [{ debugName: "absoluteStrokeWidth" }] : []));
221
+ icons = icons;
222
+ lucideIcon = computed(() => {
223
+ const iconName = this.name();
224
+ return iconName;
225
+ }, ...(ngDevMode ? [{ debugName: "lucideIcon" }] : []));
226
+ computedColor = computed(() => {
227
+ const colorValue = this.color();
228
+ const colorMap = {
229
+ 'primary': 'var(--studio-primary)',
230
+ 'secondary': 'var(--studio-secondary)',
231
+ 'success': 'var(--studio-success)',
232
+ 'error': 'var(--studio-error)',
233
+ 'warning': 'var(--studio-warning)',
234
+ 'inherit': 'currentColor'
235
+ };
236
+ return colorMap[colorValue] || colorValue;
237
+ }, ...(ngDevMode ? [{ debugName: "computedColor" }] : []));
238
+ hostClasses = computed(() => {
239
+ return ['studio-icon'];
240
+ }, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
241
+ 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: `
243
+ @if (lucideIcon(); as iconName) {
244
+ <lucide-icon
245
+ [name]="iconName"
246
+ [size]="size()"
247
+ [color]="computedColor()"
248
+ [strokeWidth]="strokeWidth()"
249
+ [absoluteStrokeWidth]="absoluteStrokeWidth()"
250
+ />
251
+ }
252
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
253
+ }
254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: IconComponent, decorators: [{
255
+ type: Component,
256
+ args: [{ selector: 'studio-icon', standalone: true, imports: [LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, host: {
257
+ '[class]': 'hostClasses()',
258
+ '[style.width.px]': 'size()',
259
+ '[style.height.px]': 'size()',
260
+ }, template: `
261
+ @if (lucideIcon(); as iconName) {
262
+ <lucide-icon
263
+ [name]="iconName"
264
+ [size]="size()"
265
+ [color]="computedColor()"
266
+ [strokeWidth]="strokeWidth()"
267
+ [absoluteStrokeWidth]="absoluteStrokeWidth()"
268
+ />
269
+ }
270
+ `, 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 }] }] } });
272
+
273
+ /**
274
+ * Провайдер иконок для Studio
275
+ * Регистрирует базовый набор часто используемых иконок
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * // app.config.ts
280
+ * import { provideStudioIcons } from '@eduboxpro/studio';
281
+ *
282
+ * export const appConfig: ApplicationConfig = {
283
+ * providers: [
284
+ * provideStudioIcons()
285
+ * ]
286
+ * };
287
+ * ```
288
+ *
289
+ * Для добавления дополнительных иконок:
290
+ * @example
291
+ * ```typescript
292
+ * import { LUCIDE_ICONS, LucideIconProvider } from 'lucide-angular';
293
+ * import { Zap, Wifi } from 'lucide-angular';
294
+ *
295
+ * providers: [
296
+ * provideStudioIcons(),
297
+ * {
298
+ * provide: LUCIDE_ICONS,
299
+ * multi: true,
300
+ * useValue: new LucideIconProvider({ Zap, Wifi })
301
+ * }
302
+ * ]
303
+ * ```
304
+ */
305
+ function provideStudioIcons() {
306
+ return makeEnvironmentProviders([
307
+ {
308
+ provide: LUCIDE_ICONS,
309
+ multi: true,
310
+ useValue: new LucideIconProvider({
311
+ ArrowRight,
312
+ ArrowLeft,
313
+ ChevronDown,
314
+ ChevronUp,
315
+ ChevronLeft,
316
+ ChevronRight,
317
+ Download,
318
+ Upload,
319
+ Mail,
320
+ Phone,
321
+ Heart,
322
+ Star,
323
+ Settings,
324
+ User,
325
+ Trash2,
326
+ Edit,
327
+ Plus,
328
+ Minus,
329
+ Check,
330
+ X,
331
+ Search,
332
+ Filter,
333
+ ExternalLink,
334
+ Link,
335
+ Copy,
336
+ Share2,
337
+ Home,
338
+ Menu,
339
+ MoreVertical,
340
+ MoreHorizontal,
341
+ Bell,
342
+ Calendar,
343
+ Clock,
344
+ Eye,
345
+ EyeOff,
346
+ Lock,
347
+ Unlock,
348
+ LogIn,
349
+ LogOut,
350
+ AlertCircle,
351
+ AlertTriangle,
352
+ Info,
353
+ CheckCircle,
354
+ XCircle,
355
+ HelpCircle,
356
+ File,
357
+ FileText,
358
+ Folder,
359
+ Image,
360
+ Save,
361
+ Printer,
362
+ RefreshCw,
363
+ RotateCw,
364
+ Loader,
365
+ Loader2,
366
+ ShoppingCart
367
+ })
368
+ }
369
+ ]);
370
+ }
371
+
372
+ /**
373
+ * Проверяет безопасность URL для навигации
374
+ *
375
+ * @param url - URL для проверки
376
+ * @returns true если URL безопасен (http/https или относительный)
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * isSafeUrl('https://example.com') // true
381
+ * isSafeUrl('javascript:alert(1)') // false
382
+ * isSafeUrl('/relative/path') // true
383
+ * ```
384
+ */
385
+ function isSafeUrl(url) {
386
+ if (!url || typeof url !== 'string') {
387
+ return false;
388
+ }
389
+ try {
390
+ const parsed = new URL(url, window.location.origin);
391
+ // Разрешаем только http(s) и относительные URL (пустой protocol)
392
+ return ['http:', 'https:', ''].includes(parsed.protocol);
393
+ }
394
+ catch {
395
+ // Невалидный URL
396
+ return false;
397
+ }
398
+ }
399
+ /**
400
+ * Sanitize URL перед использованием в навигации
401
+ *
402
+ * @param url - URL для sanitization
403
+ * @returns Безопасный URL или '#' если URL небезопасен
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * sanitizeUrl('https://example.com') // 'https://example.com'
408
+ * sanitizeUrl('javascript:alert(1)') // '#' (с предупреждением в консоли)
409
+ * ```
410
+ */
411
+ function sanitizeUrl(url) {
412
+ if (!isSafeUrl(url)) {
413
+ console.warn(`[Studio] Unsafe URL blocked: ${url}`);
414
+ return '#';
415
+ }
416
+ return url;
417
+ }
418
+
419
+ /**
420
+ * Объединяет CSS классы, игнорируя falsy значения
421
+ *
422
+ * @param classes - Массив классов (строки, undefined, null, false)
423
+ * @returns Строка с объединенными классами
424
+ *
425
+ * @example
426
+ * ```typescript
427
+ * classNames('btn', 'btn-primary') // 'btn btn-primary'
428
+ * classNames('btn', isActive && 'active') // 'btn active' если isActive=true, иначе 'btn'
429
+ * classNames('btn', undefined, null, false, 'large') // 'btn large'
430
+ * ```
431
+ */
432
+ function classNames(...classes) {
433
+ return classes.filter(Boolean).join(' ');
434
+ }
435
+
436
+ /**
437
+ * Создает computed signal с fallback chain: input → config → default
438
+ *
439
+ * Упрощает паттерн configurable inputs, убирая boilerplate код.
440
+ *
441
+ * @param inputSignal - Input signal от пользователя
442
+ * @param configSignal - Config значение (может быть undefined)
443
+ * @param defaultValue - Fallback значение
444
+ * @returns Computed signal с resolved значением
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * export class ButtonComponent {
449
+ * private defaults = computed(() => this.configService.config().components?.button);
450
+ *
451
+ * // Input
452
+ * variantInput = input<Variant | undefined>(undefined, { alias: 'variant' });
453
+ *
454
+ * // Resolved value с config fallback
455
+ * variant = withConfigDefault(
456
+ * this.variantInput,
457
+ * computed(() => this.defaults()?.variant),
458
+ * 'solid'
459
+ * );
460
+ * }
461
+ * ```
462
+ */
463
+ function withConfigDefault(inputSignal, configSignal, defaultValue) {
464
+ return computed(() => {
465
+ const inputValue = inputSignal();
466
+ if (inputValue !== undefined)
467
+ return inputValue;
468
+ const configValue = configSignal();
469
+ if (configValue !== undefined)
470
+ return configValue;
471
+ return defaultValue;
472
+ });
473
+ }
474
+
475
+ /**
476
+ * @eduboxpro/studio - Utilities
477
+ *
478
+ * Helper functions and utilities for the Studio library
479
+ */
480
+
481
+ class BadgeComponent {
482
+ configService = inject(StudioConfigService);
483
+ badgeDefaults = computed(() => this.configService.config().components?.badge, ...(ngDevMode ? [{ debugName: "badgeDefaults" }] : []));
484
+ // Appearance - inputs
485
+ variantInput = input(undefined, ...(ngDevMode ? [{ debugName: "variantInput", alias: 'variant' }] : [{ alias: 'variant' }]));
486
+ sizeInput = input(undefined, ...(ngDevMode ? [{ debugName: "sizeInput", alias: 'size' }] : [{ alias: 'size' }]));
487
+ colorInput = input(undefined, ...(ngDevMode ? [{ debugName: "colorInput", alias: 'color' }] : [{ alias: 'color' }]));
488
+ radiusInput = input(undefined, ...(ngDevMode ? [{ debugName: "radiusInput", alias: 'radius' }] : [{ alias: 'radius' }]));
489
+ // Values with config defaults
490
+ variant = withConfigDefault(this.variantInput, computed(() => this.badgeDefaults()?.variant), 'solid');
491
+ size = withConfigDefault(this.sizeInput, computed(() => this.badgeDefaults()?.size), 'md');
492
+ color = withConfigDefault(this.colorInput, computed(() => this.badgeDefaults()?.color), 'primary');
493
+ radius = withConfigDefault(this.radiusInput, computed(() => this.badgeDefaults()?.radius), 'full');
494
+ // Icon
495
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : []));
496
+ iconPosition = input('left', ...(ngDevMode ? [{ debugName: "iconPosition" }] : []));
497
+ // Dot indicator
498
+ dot = input(false, ...(ngDevMode ? [{ debugName: "dot" }] : []));
499
+ dotColor = input(...(ngDevMode ? [undefined, { debugName: "dotColor" }] : []));
500
+ // Removable
501
+ removable = input(false, ...(ngDevMode ? [{ debugName: "removable" }] : []));
502
+ removed = output();
503
+ // Clickable & Navigation
504
+ href = input(...(ngDevMode ? [undefined, { debugName: "href" }] : []));
505
+ target = input('_self', ...(ngDevMode ? [{ debugName: "target" }] : []));
506
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
507
+ clicked = output();
508
+ // Number badge
509
+ value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
510
+ max = input(...(ngDevMode ? [undefined, { debugName: "max" }] : []));
511
+ showZero = input(true, ...(ngDevMode ? [{ debugName: "showZero" }] : []));
512
+ // Typography
513
+ uppercase = input(false, ...(ngDevMode ? [{ debugName: "uppercase" }] : []));
514
+ bold = input(false, ...(ngDevMode ? [{ debugName: "bold" }] : []));
515
+ // Animation
516
+ pulse = input(false, ...(ngDevMode ? [{ debugName: "pulse" }] : []));
517
+ // Auto color (будет реализовано позже если нужно)
518
+ autoColor = input(false, ...(ngDevMode ? [{ debugName: "autoColor" }] : []));
519
+ iconSize = computed(() => {
520
+ const sizeMap = { sm: 12, md: 14, lg: 16 };
521
+ return sizeMap[this.size()];
522
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
523
+ displayValue = computed(() => {
524
+ const val = this.value();
525
+ if (val === undefined || val === null)
526
+ return '';
527
+ const numVal = typeof val === 'number' ? val : parseInt(val, 10);
528
+ // Если это число
529
+ if (!isNaN(numVal)) {
530
+ // Не показывать 0 если showZero = false
531
+ if (numVal === 0 && !this.showZero())
532
+ return '';
533
+ // Показать max+ если превышает максимум
534
+ const maxVal = this.max();
535
+ if (maxVal !== undefined && numVal > maxVal) {
536
+ return `${maxVal}+`;
537
+ }
538
+ return numVal.toString();
539
+ }
540
+ return val.toString();
541
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
542
+ isVisible = computed(() => {
543
+ // Скрыть если значение 0 и showZero = false
544
+ const val = this.value();
545
+ if (val !== undefined && val !== null) {
546
+ const numVal = typeof val === 'number' ? val : parseInt(val, 10);
547
+ if (!isNaN(numVal) && numVal === 0 && !this.showZero()) {
548
+ return false;
549
+ }
550
+ }
551
+ return true;
552
+ }, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
553
+ 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" }] : []));
554
+ handleClick(event) {
555
+ if (this.disabled()) {
556
+ event.preventDefault();
557
+ event.stopPropagation();
558
+ return;
559
+ }
560
+ // Handle link navigation
561
+ const url = this.href();
562
+ if (url) {
563
+ const safeUrl = sanitizeUrl(url);
564
+ if (safeUrl === '#') {
565
+ // URL was blocked - don't navigate
566
+ return;
567
+ }
568
+ const target = this.target();
569
+ if (target === '_blank') {
570
+ window.open(safeUrl, '_blank', 'noopener,noreferrer');
571
+ }
572
+ else {
573
+ window.location.href = safeUrl;
574
+ }
575
+ }
576
+ this.clicked.emit(event);
577
+ }
578
+ handleRemove(event) {
579
+ event.preventDefault();
580
+ event.stopPropagation();
581
+ if (this.disabled())
582
+ return;
583
+ this.removed.emit();
584
+ }
585
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: BadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
586
+ 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: `
587
+ <span class="studio-badge__content">
588
+ @if (dot()) {
589
+ <span
590
+ class="studio-badge__dot"
591
+ [class]="'studio-badge__dot--' + (dotColor() || color())"
592
+ ></span>
593
+ }
594
+ @if (icon() && iconPosition() === 'left') {
595
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
596
+ }
597
+ <span class="studio-badge__text">
598
+ @if (displayValue()) {
599
+ {{ displayValue() }}
600
+ } @else {
601
+ <ng-content />
602
+ }
603
+ </span>
604
+ @if (icon() && iconPosition() === 'right') {
605
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
606
+ }
607
+ @if (removable()) {
608
+ <button
609
+ type="button"
610
+ class="studio-badge__remove"
611
+ (click)="handleRemove($event)"
612
+ aria-label="Remove"
613
+ >
614
+ <studio-icon name="x" [size]="iconSize()" />
615
+ </button>
616
+ }
617
+ </span>
618
+ `, 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
619
+ }
620
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: BadgeComponent, decorators: [{
621
+ type: Component,
622
+ args: [{ selector: 'studio-badge', standalone: true, imports: [IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
623
+ '[class]': 'hostClasses()',
624
+ '[attr.disabled]': 'disabled() ? "" : null',
625
+ '[attr.href]': 'href()',
626
+ '[attr.target]': 'href() ? target() : null',
627
+ '[attr.rel]': 'href() && target() === "_blank" ? "noopener noreferrer" : null',
628
+ '[style.display]': 'isVisible() ? null : "none"',
629
+ '(click)': 'handleClick($event)'
630
+ }, template: `
631
+ <span class="studio-badge__content">
632
+ @if (dot()) {
633
+ <span
634
+ class="studio-badge__dot"
635
+ [class]="'studio-badge__dot--' + (dotColor() || color())"
636
+ ></span>
637
+ }
638
+ @if (icon() && iconPosition() === 'left') {
639
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
640
+ }
641
+ <span class="studio-badge__text">
642
+ @if (displayValue()) {
643
+ {{ displayValue() }}
644
+ } @else {
645
+ <ng-content />
646
+ }
647
+ </span>
648
+ @if (icon() && iconPosition() === 'right') {
649
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
650
+ }
651
+ @if (removable()) {
652
+ <button
653
+ type="button"
654
+ class="studio-badge__remove"
655
+ (click)="handleRemove($event)"
656
+ aria-label="Remove"
657
+ >
658
+ <studio-icon name="x" [size]="iconSize()" />
659
+ </button>
660
+ }
661
+ </span>
662
+ `, 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"] }]
663
+ }], 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 }] }] } });
664
+
665
+ /**
666
+ * Badge component
667
+ */
668
+
136
669
  class ButtonComponent {
137
- variant = input('solid', ...(ngDevMode ? [{ debugName: "variant" }] : []));
138
- size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
139
- color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : []));
670
+ configService = inject(StudioConfigService);
671
+ buttonDefaults = computed(() => this.configService.config().components?.button, ...(ngDevMode ? [{ debugName: "buttonDefaults" }] : []));
672
+ // Appearance - inputs
673
+ variantInput = input(undefined, ...(ngDevMode ? [{ debugName: "variantInput", alias: 'variant' }] : [{ alias: 'variant' }]));
674
+ sizeInput = input(undefined, ...(ngDevMode ? [{ debugName: "sizeInput", alias: 'size' }] : [{ alias: 'size' }]));
675
+ colorInput = input(undefined, ...(ngDevMode ? [{ debugName: "colorInput", alias: 'color' }] : [{ alias: 'color' }]));
676
+ radiusInput = input(undefined, ...(ngDevMode ? [{ debugName: "radiusInput", alias: 'radius' }] : [{ alias: 'radius' }]));
677
+ shadowInput = input(undefined, ...(ngDevMode ? [{ debugName: "shadowInput", alias: 'shadow' }] : [{ alias: 'shadow' }]));
678
+ compactInput = input(undefined, ...(ngDevMode ? [{ debugName: "compactInput", alias: 'compact' }] : [{ alias: 'compact' }]));
679
+ // Values with config defaults
680
+ variant = withConfigDefault(this.variantInput, computed(() => this.buttonDefaults()?.variant), 'solid');
681
+ size = withConfigDefault(this.sizeInput, computed(() => this.buttonDefaults()?.size), 'md');
682
+ color = withConfigDefault(this.colorInput, computed(() => this.buttonDefaults()?.color), 'primary');
683
+ radius = withConfigDefault(this.radiusInput, computed(() => this.buttonDefaults()?.radius), 'sm');
684
+ shadow = withConfigDefault(this.shadowInput, computed(() => this.buttonDefaults()?.shadow), 'none');
685
+ compact = withConfigDefault(this.compactInput, computed(() => this.buttonDefaults()?.compact), false);
686
+ // State
140
687
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
141
- fullWidth = input(false, ...(ngDevMode ? [{ debugName: "fullWidth" }] : []));
142
688
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
689
+ loadingText = input('Loading...', ...(ngDevMode ? [{ debugName: "loadingText" }] : []));
690
+ // Layout
691
+ fullWidth = input(false, ...(ngDevMode ? [{ debugName: "fullWidth" }] : []));
692
+ // Button props
143
693
  type = input('button', ...(ngDevMode ? [{ debugName: "type" }] : []));
694
+ // Icon
695
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : []));
696
+ iconPosition = input('left', ...(ngDevMode ? [{ debugName: "iconPosition" }] : []));
697
+ // Link mode
698
+ href = input(...(ngDevMode ? [undefined, { debugName: "href" }] : []));
699
+ target = input('_self', ...(ngDevMode ? [{ debugName: "target" }] : []));
700
+ // Badge
701
+ badge = input(...(ngDevMode ? [undefined, { debugName: "badge" }] : []));
702
+ badgeColor = input('error', ...(ngDevMode ? [{ debugName: "badgeColor" }] : []));
703
+ // Accessibility
704
+ ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : []));
144
705
  clicked = output();
145
- hostClasses = computed(() => {
146
- return [
147
- 'studio-button',
148
- `studio-button--${this.variant()}`,
149
- `studio-button--${this.size()}`,
150
- `studio-button--${this.color()}`,
151
- this.fullWidth() ? 'studio-button--full' : '',
152
- this.loading() ? 'studio-button--loading' : ''
153
- ].filter(Boolean).join(' ');
154
- }, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
706
+ iconSize = computed(() => {
707
+ const sizeMap = { sm: 16, md: 18, lg: 20 };
708
+ return sizeMap[this.size()];
709
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
710
+ 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" }] : []));
155
711
  handleClick(event) {
156
712
  if (this.disabled() || this.loading()) {
157
713
  event.preventDefault();
158
714
  event.stopPropagation();
159
715
  return;
160
716
  }
717
+ // Handle link navigation
718
+ const url = this.href();
719
+ if (url) {
720
+ const safeUrl = sanitizeUrl(url);
721
+ if (safeUrl === '#') {
722
+ // URL was blocked - don't navigate
723
+ return;
724
+ }
725
+ const target = this.target();
726
+ if (target === '_blank') {
727
+ window.open(safeUrl, '_blank', 'noopener,noreferrer');
728
+ }
729
+ else {
730
+ window.location.href = safeUrl;
731
+ }
732
+ }
161
733
  this.clicked.emit(event);
162
734
  }
163
735
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
164
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.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 } }, outputs: { clicked: "clicked" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "hostClasses()", "attr.disabled": "disabled() ? \"\" : null", "attr.type": "type()" } }, ngImport: i0, template: `
736
+ 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: `
165
737
  <span class="studio-button__content">
166
- <ng-content />
738
+ @if (loading()) {
739
+ <studio-icon
740
+ name="loader-2"
741
+ [size]="iconSize()"
742
+ class="studio-button__spinner"
743
+ />
744
+ }
745
+ @if (!loading() && icon() && iconPosition() === 'left') {
746
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
747
+ }
748
+ @if (iconPosition() !== 'only' && !loading()) {
749
+ <ng-content />
750
+ }
751
+ @if (!loading() && icon() && iconPosition() === 'right') {
752
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
753
+ }
754
+ @if (!loading() && icon() && iconPosition() === 'only') {
755
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
756
+ }
757
+ @if (loading() && iconPosition() === 'only') {
758
+ <!-- Empty, spinner already shown -->
759
+ } @else if (loading()) {
760
+ <span>{{ loadingText() }}</span>
761
+ }
762
+ @if (badge() !== undefined && badge() !== null && badge() !== '') {
763
+ <span class="studio-button__badge studio-button__badge--{{ badgeColor() }}">
764
+ {{ badge() }}
765
+ </span>
766
+ }
167
767
  </span>
168
- `, isInline: true, styles: [":host{display:inline-block}.studio-button{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);border-radius:var(--studio-radius-md)}.studio-button:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}.studio-button:active:not(:disabled):not(.studio-button--loading){transform:scale(.98)}.studio-button--sm{padding:var(--studio-spacing-xs) var(--studio-spacing-sm);font-size:var(--studio-font-size-sm);height:2rem;min-width:2rem}.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}.studio-button--lg{padding:var(--studio-spacing-md) var(--studio-spacing-lg);font-size:var(--studio-font-size-lg);height:3rem;min-width:3rem}.studio-button--solid.studio-button--primary{background:var(--studio-primary);color:#fff}.studio-button--solid.studio-button--primary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-primary-hover)}.studio-button--solid.studio-button--primary:active:not(:disabled):not(.studio-button--loading){background:var(--studio-primary-active)}.studio-button--outline.studio-button--primary{background:transparent;border:1px solid var(--studio-primary);color:var(--studio-primary)}.studio-button--outline.studio-button--primary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-primary);color:#fff}.studio-button--ghost.studio-button--primary{background:transparent;color:var(--studio-primary)}.studio-button--ghost.studio-button--primary:hover:not(:disabled):not(.studio-button--loading){background:#3b82f61a}.studio-button--solid.studio-button--secondary{background:var(--studio-secondary);color:#fff}.studio-button--solid.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-secondary-hover)}.studio-button--solid.studio-button--secondary:active:not(:disabled):not(.studio-button--loading){background:var(--studio-secondary-active)}.studio-button--outline.studio-button--secondary{background:transparent;border:1px solid var(--studio-secondary);color:var(--studio-secondary)}.studio-button--outline.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-secondary);color:#fff}.studio-button--ghost.studio-button--secondary{background:transparent;color:var(--studio-secondary)}.studio-button--ghost.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading){background:#8b5cf61a}.studio-button--solid.studio-button--success{background:var(--studio-success);color:#fff}.studio-button--solid.studio-button--success:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-success-hover)}.studio-button--outline.studio-button--success{background:transparent;border:1px solid var(--studio-success);color:var(--studio-success)}.studio-button--outline.studio-button--success:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-success);color:#fff}.studio-button--ghost.studio-button--success{background:transparent;color:var(--studio-success)}.studio-button--ghost.studio-button--success:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-success-bg)}.studio-button--solid.studio-button--error{background:var(--studio-error);color:#fff}.studio-button--solid.studio-button--error:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-error-hover)}.studio-button--outline.studio-button--error{background:transparent;border:1px solid var(--studio-error);color:var(--studio-error)}.studio-button--outline.studio-button--error:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-error);color:#fff}.studio-button--ghost.studio-button--error{background:transparent;color:var(--studio-error)}.studio-button--ghost.studio-button--error:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-error-bg)}.studio-button--solid.studio-button--warning{background:var(--studio-warning);color:#fff}.studio-button--solid.studio-button--warning:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-warning-hover)}.studio-button--outline.studio-button--warning{background:transparent;border:1px solid var(--studio-warning);color:var(--studio-warning)}.studio-button--outline.studio-button--warning:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-warning);color:#fff}.studio-button--ghost.studio-button--warning{background:transparent;color:var(--studio-warning)}.studio-button--ghost.studio-button--warning:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-warning-bg)}.studio-button--full{width:100%}.studio-button--loading{cursor:wait;opacity:.7}.studio-button:disabled{opacity:.5;cursor:not-allowed}.studio-button__content{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}[data-theme=dark] .studio-button--solid.studio-button--primary,[data-theme=dark] .studio-button--solid.studio-button--secondary,[data-theme=dark] .studio-button--solid.studio-button--success,[data-theme=dark] .studio-button--solid.studio-button--error,[data-theme=dark] .studio-button--solid.studio-button--warning{box-shadow:var(--studio-shadow-sm)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
768
+ `, 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:#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)}}.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
169
769
  }
170
770
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ButtonComponent, decorators: [{
171
771
  type: Component,
172
- args: [{ selector: 'studio-button', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, host: {
772
+ args: [{ selector: 'studio-button', standalone: true, imports: [IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
173
773
  '[class]': 'hostClasses()',
174
- '[attr.disabled]': 'disabled() ? "" : null',
175
- '[attr.type]': 'type()',
774
+ '[attr.disabled]': 'disabled() || loading() ? "" : null',
775
+ '[attr.type]': 'href() ? null : type()',
776
+ '[attr.href]': 'href()',
777
+ '[attr.target]': 'href() ? target() : null',
778
+ '[attr.rel]': 'href() && target() === "_blank" ? "noopener noreferrer" : null',
779
+ '[attr.aria-label]': 'ariaLabel()',
176
780
  '(click)': 'handleClick($event)'
177
781
  }, template: `
178
782
  <span class="studio-button__content">
179
- <ng-content />
783
+ @if (loading()) {
784
+ <studio-icon
785
+ name="loader-2"
786
+ [size]="iconSize()"
787
+ class="studio-button__spinner"
788
+ />
789
+ }
790
+ @if (!loading() && icon() && iconPosition() === 'left') {
791
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
792
+ }
793
+ @if (iconPosition() !== 'only' && !loading()) {
794
+ <ng-content />
795
+ }
796
+ @if (!loading() && icon() && iconPosition() === 'right') {
797
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
798
+ }
799
+ @if (!loading() && icon() && iconPosition() === 'only') {
800
+ <studio-icon [name]="icon()!" [size]="iconSize()" />
801
+ }
802
+ @if (loading() && iconPosition() === 'only') {
803
+ <!-- Empty, spinner already shown -->
804
+ } @else if (loading()) {
805
+ <span>{{ loadingText() }}</span>
806
+ }
807
+ @if (badge() !== undefined && badge() !== null && badge() !== '') {
808
+ <span class="studio-button__badge studio-button__badge--{{ badgeColor() }}">
809
+ {{ badge() }}
810
+ </span>
811
+ }
180
812
  </span>
181
- `, styles: [":host{display:inline-block}.studio-button{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);border-radius:var(--studio-radius-md)}.studio-button:focus-visible{outline:2px solid var(--studio-primary);outline-offset:2px}.studio-button:active:not(:disabled):not(.studio-button--loading){transform:scale(.98)}.studio-button--sm{padding:var(--studio-spacing-xs) var(--studio-spacing-sm);font-size:var(--studio-font-size-sm);height:2rem;min-width:2rem}.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}.studio-button--lg{padding:var(--studio-spacing-md) var(--studio-spacing-lg);font-size:var(--studio-font-size-lg);height:3rem;min-width:3rem}.studio-button--solid.studio-button--primary{background:var(--studio-primary);color:#fff}.studio-button--solid.studio-button--primary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-primary-hover)}.studio-button--solid.studio-button--primary:active:not(:disabled):not(.studio-button--loading){background:var(--studio-primary-active)}.studio-button--outline.studio-button--primary{background:transparent;border:1px solid var(--studio-primary);color:var(--studio-primary)}.studio-button--outline.studio-button--primary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-primary);color:#fff}.studio-button--ghost.studio-button--primary{background:transparent;color:var(--studio-primary)}.studio-button--ghost.studio-button--primary:hover:not(:disabled):not(.studio-button--loading){background:#3b82f61a}.studio-button--solid.studio-button--secondary{background:var(--studio-secondary);color:#fff}.studio-button--solid.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-secondary-hover)}.studio-button--solid.studio-button--secondary:active:not(:disabled):not(.studio-button--loading){background:var(--studio-secondary-active)}.studio-button--outline.studio-button--secondary{background:transparent;border:1px solid var(--studio-secondary);color:var(--studio-secondary)}.studio-button--outline.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-secondary);color:#fff}.studio-button--ghost.studio-button--secondary{background:transparent;color:var(--studio-secondary)}.studio-button--ghost.studio-button--secondary:hover:not(:disabled):not(.studio-button--loading){background:#8b5cf61a}.studio-button--solid.studio-button--success{background:var(--studio-success);color:#fff}.studio-button--solid.studio-button--success:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-success-hover)}.studio-button--outline.studio-button--success{background:transparent;border:1px solid var(--studio-success);color:var(--studio-success)}.studio-button--outline.studio-button--success:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-success);color:#fff}.studio-button--ghost.studio-button--success{background:transparent;color:var(--studio-success)}.studio-button--ghost.studio-button--success:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-success-bg)}.studio-button--solid.studio-button--error{background:var(--studio-error);color:#fff}.studio-button--solid.studio-button--error:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-error-hover)}.studio-button--outline.studio-button--error{background:transparent;border:1px solid var(--studio-error);color:var(--studio-error)}.studio-button--outline.studio-button--error:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-error);color:#fff}.studio-button--ghost.studio-button--error{background:transparent;color:var(--studio-error)}.studio-button--ghost.studio-button--error:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-error-bg)}.studio-button--solid.studio-button--warning{background:var(--studio-warning);color:#fff}.studio-button--solid.studio-button--warning:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-warning-hover)}.studio-button--outline.studio-button--warning{background:transparent;border:1px solid var(--studio-warning);color:var(--studio-warning)}.studio-button--outline.studio-button--warning:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-warning);color:#fff}.studio-button--ghost.studio-button--warning{background:transparent;color:var(--studio-warning)}.studio-button--ghost.studio-button--warning:hover:not(:disabled):not(.studio-button--loading){background:var(--studio-warning-bg)}.studio-button--full{width:100%}.studio-button--loading{cursor:wait;opacity:.7}.studio-button:disabled{opacity:.5;cursor:not-allowed}.studio-button__content{display:inline-flex;align-items:center;gap:var(--studio-spacing-sm)}[data-theme=dark] .studio-button--solid.studio-button--primary,[data-theme=dark] .studio-button--solid.studio-button--secondary,[data-theme=dark] .studio-button--solid.studio-button--success,[data-theme=dark] .studio-button--solid.studio-button--error,[data-theme=dark] .studio-button--solid.studio-button--warning{box-shadow:var(--studio-shadow-sm)}\n"] }]
182
- }], 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 }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
813
+ `, 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:#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)}}.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"] }]
814
+ }], 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"] }] } });
183
815
 
184
816
  /**
185
817
  * Button component
@@ -200,11 +832,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
200
832
  * Domain-specific components for educational platforms
201
833
  */
202
834
 
203
- /**
204
- * Utilities
205
- * Helper functions, directives, pipes
206
- */
207
-
208
835
  /**
209
836
  * Public API Surface of @eduboxpro/studio
210
837
  */
@@ -216,5 +843,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
216
843
  * Generated bundle index. Do not edit.
217
844
  */
218
845
 
219
- export { ButtonComponent, STUDIO_CONFIG, StudioConfigService, provideStudioConfig };
846
+ export { BadgeComponent, ButtonComponent, IconComponent, STUDIO_CONFIG, StudioConfigService, classNames, isSafeUrl, provideStudioConfig, provideStudioIcons, sanitizeUrl, withConfigDefault };
220
847
  //# sourceMappingURL=eduboxpro-studio.mjs.map