UNPKG

bakery-ui

Version:

🥖 A delicious Angular UI component library with bakery-inspired theming and Storybook integration

1,131 lines (1,080 loc) 44.2 kB
import * as i0 from '@angular/core'; import { Component, EventEmitter, Output, Input, ChangeDetectionStrategy, forwardRef } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import * as i2 from '@angular/material/core'; import { MatRippleModule } from '@angular/material/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatCardModule } from '@angular/material/card'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { trigger, state, transition, style, animate } from '@angular/animations'; class BakeryUi { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BakeryUi, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BakeryUi, isStandalone: true, selector: "lib-bakery-ui", ngImport: i0, template: ` <p> bakery-ui works! </p> `, isInline: true, styles: [""] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BakeryUi, decorators: [{ type: Component, args: [{ selector: 'lib-bakery-ui', imports: [], template: ` <p> bakery-ui works! </p> ` }] }] }); class BaguetteButton { variant = 'primary'; size = 'md'; disabled = false; loading = false; fullWidth = false; leftIcon; rightIcon; ariaLabel; ariaDescribedBy; clicked = new EventEmitter(); handleClick(event) { if (!this.disabled && !this.loading) { this.clicked.emit(event); } } get buttonClasses() { const baseClasses = [ 'inline-flex items-center justify-center font-medium rounded-lg', 'transition-all duration-200 ease-in-out', 'focus:outline-none focus:ring-2 focus:ring-offset-2', 'disabled:opacity-50 disabled:cursor-not-allowed', 'transform hover:scale-105 active:scale-95', 'shadow-sm hover:shadow-md' ]; // Size classes - made more compact const sizeClasses = { sm: 'px-2 py-1 text-xs', md: 'px-3 py-1.5 text-sm', lg: 'px-4 py-2 text-base', xl: 'px-5 py-2.5 text-lg' }; // Variant classes with bakery theme const variantClasses = { primary: [ 'bg-gradient-to-r from-amber-400 to-yellow-500', 'hover:from-amber-500 hover:to-yellow-600', 'text-amber-900 font-semibold', 'border border-amber-300', 'focus:ring-amber-500', 'shadow-amber-200' ], secondary: [ 'bg-gradient-to-r from-orange-100 to-amber-100', 'hover:from-orange-200 hover:to-amber-200', 'text-amber-800 font-medium', 'border border-amber-200', 'focus:ring-amber-400' ], outline: [ 'bg-transparent border-2 border-amber-400', 'hover:bg-amber-50 hover:border-amber-500', 'text-amber-700 font-medium', 'focus:ring-amber-400' ], ghost: [ 'bg-transparent hover:bg-amber-50', 'text-amber-700 font-medium', 'focus:ring-amber-400' ], danger: [ 'bg-gradient-to-r from-red-500 to-red-600', 'hover:from-red-600 hover:to-red-700', 'text-white font-medium', 'border border-red-400', 'focus:ring-red-500', 'shadow-red-200' ] }; const classes = [ ...baseClasses, sizeClasses[this.size], ...variantClasses[this.variant] ]; if (this.fullWidth) { classes.push('w-full'); } return classes.join(' '); } get contentClasses() { return this.loading ? 'opacity-70' : ''; } get rippleColor() { const rippleColors = { primary: 'rgba(245, 158, 11, 0.3)', secondary: 'rgba(217, 119, 6, 0.3)', outline: 'rgba(245, 158, 11, 0.2)', ghost: 'rgba(245, 158, 11, 0.1)', danger: 'rgba(239, 68, 68, 0.3)' }; return rippleColors[this.variant]; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BaguetteButton, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BaguetteButton, isStandalone: true, selector: "baguette-button", inputs: { variant: "variant", size: "size", disabled: "disabled", loading: "loading", fullWidth: "fullWidth", leftIcon: "leftIcon", rightIcon: "rightIcon", ariaLabel: "ariaLabel", ariaDescribedBy: "ariaDescribedBy" }, outputs: { clicked: "clicked" }, ngImport: i0, template: ` <button [class]="buttonClasses" [disabled]="disabled || loading" [attr.aria-label]="ariaLabel" [attr.aria-describedby]="ariaDescribedBy" (click)="handleClick($event)" matRipple [matRippleDisabled]="disabled || loading" [matRippleColor]="rippleColor"> <!-- Loading spinner --> <svg *ngIf="loading" class="animate-spin -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> <!-- Left icon --> <span *ngIf="leftIcon && !loading" [innerHTML]="leftIcon" class="mr-2"></span> <!-- Button content --> <span [class]="contentClasses"> <ng-content></ng-content> </span> <!-- Right icon --> <span *ngIf="rightIcon" [innerHTML]="rightIcon" class="ml-2"></span> </button> `, isInline: true, styles: [":host{display:inline-block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i2.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BaguetteButton, decorators: [{ type: Component, args: [{ selector: 'baguette-button', standalone: true, imports: [CommonModule, MatButtonModule, MatRippleModule], changeDetection: ChangeDetectionStrategy.OnPush, template: ` <button [class]="buttonClasses" [disabled]="disabled || loading" [attr.aria-label]="ariaLabel" [attr.aria-describedby]="ariaDescribedBy" (click)="handleClick($event)" matRipple [matRippleDisabled]="disabled || loading" [matRippleColor]="rippleColor"> <!-- Loading spinner --> <svg *ngIf="loading" class="animate-spin -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> <!-- Left icon --> <span *ngIf="leftIcon && !loading" [innerHTML]="leftIcon" class="mr-2"></span> <!-- Button content --> <span [class]="contentClasses"> <ng-content></ng-content> </span> <!-- Right icon --> <span *ngIf="rightIcon" [innerHTML]="rightIcon" class="ml-2"></span> </button> `, styles: [":host{display:inline-block}\n"] }] }], propDecorators: { variant: [{ type: Input }], size: [{ type: Input }], disabled: [{ type: Input }], loading: [{ type: Input }], fullWidth: [{ type: Input }], leftIcon: [{ type: Input }], rightIcon: [{ type: Input }], ariaLabel: [{ type: Input }], ariaDescribedBy: [{ type: Input }], clicked: [{ type: Output }] } }); class BagelInput { label; placeholder; type = 'text'; size = 'md'; disabled = false; readonly = false; required = false; clearable = false; leftIcon; rightIcon; helperText; error; ariaLabel; ariaDescribedBy; autocomplete; maxLength; minLength; min; max; step; showCharCount = false; inputId = `bagel-input-${Math.random().toString(36).substring(2, 9)}`; valueChange = new EventEmitter(); inputFocus = new EventEmitter(); inputBlur = new EventEmitter(); enterPressed = new EventEmitter(); value = ''; onChange = (value) => { }; onTouched = () => { }; // ControlValueAccessor implementation writeValue(value) { this.value = value || ''; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouched = fn; } setDisabledState(isDisabled) { this.disabled = isDisabled; } onInput(event) { const target = event.target; this.value = target.value; this.onChange(this.value); this.valueChange.emit(this.value); } onBlur() { this.onTouched(); this.inputBlur.emit(); } onFocus() { this.inputFocus.emit(); } onEnter() { this.enterPressed.emit(); } clearValue() { this.value = ''; this.onChange(this.value); this.valueChange.emit(this.value); } get labelClasses() { return [ 'block text-xs font-medium mb-1', this.error ? 'text-red-700' : 'text-amber-800' ].join(' '); } get containerClasses() { return 'relative'; } get inputClasses() { const baseClasses = [ 'block w-full rounded-lg border-2 transition-all duration-200', 'focus:outline-none focus:ring-2 focus:ring-offset-1', 'disabled:opacity-50 disabled:cursor-not-allowed', 'placeholder-amber-400' ]; const sizeClasses = { sm: 'px-2 py-1 text-xs', md: 'px-3 py-1.5 text-sm', lg: 'px-4 py-2 text-base' }; const stateClasses = this.error ? [ 'border-red-300 bg-red-50', 'focus:border-red-500 focus:ring-red-200', 'text-red-900' ] : [ 'border-amber-300 bg-amber-50', 'focus:border-amber-500 focus:ring-amber-200', 'text-amber-900', 'hover:border-amber-400' ]; const paddingAdjustment = []; if (this.leftIcon) paddingAdjustment.push('pl-10'); if (this.rightIcon || this.clearable) paddingAdjustment.push('pr-10'); return [ ...baseClasses, sizeClasses[this.size], ...stateClasses, ...paddingAdjustment ].join(' '); } get helperClasses() { return 'mt-0.5 text-xs text-amber-600'; } get errorClasses() { return 'mt-0.5 text-xs text-red-600 flex items-center'; } get charCountClasses() { const isNearLimit = this.maxLength && this.value?.length > this.maxLength * 0.8; return [ 'mt-1 text-xs text-right', isNearLimit ? 'text-red-500' : 'text-amber-500' ].join(' '); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BagelInput, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BagelInput, isStandalone: true, selector: "bagel-input", inputs: { label: "label", placeholder: "placeholder", type: "type", size: "size", disabled: "disabled", readonly: "readonly", required: "required", clearable: "clearable", leftIcon: "leftIcon", rightIcon: "rightIcon", helperText: "helperText", error: "error", ariaLabel: "ariaLabel", ariaDescribedBy: "ariaDescribedBy", autocomplete: "autocomplete", maxLength: "maxLength", minLength: "minLength", min: "min", max: "max", step: "step", showCharCount: "showCharCount", inputId: "inputId" }, outputs: { valueChange: "valueChange", inputFocus: "inputFocus", inputBlur: "inputBlur", enterPressed: "enterPressed" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => BagelInput), multi: true } ], ngImport: i0, template: ` <div class="w-full"> <!-- Label --> <label *ngIf="label" [for]="inputId" [class]="labelClasses"> {{ label }} <span *ngIf="required" class="text-red-500 ml-1">*</span> </label> <!-- Input Container --> <div [class]="containerClasses"> <!-- Left Icon --> <div *ngIf="leftIcon" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <span [innerHTML]="leftIcon" class="text-amber-600"></span> </div> <!-- Input Field --> <input [id]="inputId" [type]="type" [value]="value" [placeholder]="placeholder" [disabled]="disabled" [readonly]="readonly" [class]="inputClasses" [attr.aria-label]="ariaLabel" [attr.aria-describedby]="ariaDescribedBy" [attr.autocomplete]="autocomplete" [attr.maxlength]="maxLength" [attr.minlength]="minLength" [attr.min]="min" [attr.max]="max" [attr.step]="step" (input)="onInput($event)" (blur)="onBlur()" (focus)="onFocus()" (keyup.enter)="onEnter()" /> <!-- Right Icon / Clear Button --> <div *ngIf="rightIcon || (clearable && value)" class="absolute inset-y-0 right-0 flex items-center"> <button *ngIf="clearable && value && !disabled" type="button" class="pr-3 text-amber-600 hover:text-amber-700 focus:outline-none" (click)="clearValue()" [attr.aria-label]="'Clear ' + (label || 'input')"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <span *ngIf="rightIcon" [innerHTML]="rightIcon" class="pr-3 text-amber-600"></span> </div> </div> <!-- Helper Text --> <p *ngIf="helperText && !error" [class]="helperClasses"> {{ helperText }} </p> <!-- Error Message --> <p *ngIf="error" [class]="errorClasses"> {{ error }} </p> <!-- Character Count --> <p *ngIf="maxLength && showCharCount" [class]="charCountClasses"> {{ value?.length || 0 }}/{{ maxLength }} </p> </div> `, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BagelInput, decorators: [{ type: Component, args: [{ selector: 'bagel-input', standalone: true, imports: [CommonModule, MatFormFieldModule, MatInputModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => BagelInput), multi: true } ], template: ` <div class="w-full"> <!-- Label --> <label *ngIf="label" [for]="inputId" [class]="labelClasses"> {{ label }} <span *ngIf="required" class="text-red-500 ml-1">*</span> </label> <!-- Input Container --> <div [class]="containerClasses"> <!-- Left Icon --> <div *ngIf="leftIcon" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <span [innerHTML]="leftIcon" class="text-amber-600"></span> </div> <!-- Input Field --> <input [id]="inputId" [type]="type" [value]="value" [placeholder]="placeholder" [disabled]="disabled" [readonly]="readonly" [class]="inputClasses" [attr.aria-label]="ariaLabel" [attr.aria-describedby]="ariaDescribedBy" [attr.autocomplete]="autocomplete" [attr.maxlength]="maxLength" [attr.minlength]="minLength" [attr.min]="min" [attr.max]="max" [attr.step]="step" (input)="onInput($event)" (blur)="onBlur()" (focus)="onFocus()" (keyup.enter)="onEnter()" /> <!-- Right Icon / Clear Button --> <div *ngIf="rightIcon || (clearable && value)" class="absolute inset-y-0 right-0 flex items-center"> <button *ngIf="clearable && value && !disabled" type="button" class="pr-3 text-amber-600 hover:text-amber-700 focus:outline-none" (click)="clearValue()" [attr.aria-label]="'Clear ' + (label || 'input')"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <span *ngIf="rightIcon" [innerHTML]="rightIcon" class="pr-3 text-amber-600"></span> </div> </div> <!-- Helper Text --> <p *ngIf="helperText && !error" [class]="helperClasses"> {{ helperText }} </p> <!-- Error Message --> <p *ngIf="error" [class]="errorClasses"> {{ error }} </p> <!-- Character Count --> <p *ngIf="maxLength && showCharCount" [class]="charCountClasses"> {{ value?.length || 0 }}/{{ maxLength }} </p> </div> `, styles: [":host{display:block;width:100%}\n"] }] }], propDecorators: { label: [{ type: Input }], placeholder: [{ type: Input }], type: [{ type: Input }], size: [{ type: Input }], disabled: [{ type: Input }], readonly: [{ type: Input }], required: [{ type: Input }], clearable: [{ type: Input }], leftIcon: [{ type: Input }], rightIcon: [{ type: Input }], helperText: [{ type: Input }], error: [{ type: Input }], ariaLabel: [{ type: Input }], ariaDescribedBy: [{ type: Input }], autocomplete: [{ type: Input }], maxLength: [{ type: Input }], minLength: [{ type: Input }], min: [{ type: Input }], max: [{ type: Input }], step: [{ type: Input }], showCharCount: [{ type: Input }], inputId: [{ type: Input }], valueChange: [{ type: Output }], inputFocus: [{ type: Output }], inputBlur: [{ type: Output }], enterPressed: [{ type: Output }] } }); class CupcakeCard { variant = 'default'; size = 'md'; title; subtitle; image; imageAlt; imageLoading = 'lazy'; imageBadge; avatar; avatarAlt; clickable = false; disabled = false; loading = false; headerAction = false; hasHeader = false; hasFooter = false; ariaLabel; fullWidth = false; cardClick = new EventEmitter(); handleClick(event) { if (this.clickable && !this.disabled && !this.loading) { this.cardClick.emit(event); } } get cardClasses() { const baseClasses = [ 'rounded-xl overflow-hidden transition-all duration-200', 'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-amber-500' ]; const sizeClasses = { sm: 'max-w-xs', md: 'max-w-sm', lg: 'max-w-md' }; const variantClasses = { default: [ 'bg-white border border-amber-200', 'hover:border-amber-300 hover:shadow-md' ], outlined: [ 'bg-transparent border-2 border-amber-300', 'hover:border-amber-400 hover:bg-amber-50' ], elevated: [ 'bg-white shadow-lg shadow-amber-100', 'hover:shadow-xl hover:shadow-amber-200', 'border border-amber-100' ], filled: [ 'bg-gradient-to-br from-amber-50 to-orange-50', 'border border-amber-200', 'hover:from-amber-100 hover:to-orange-100' ] }; const interactionClasses = []; if (this.clickable) { interactionClasses.push('cursor-pointer', 'hover:transform hover:scale-105', 'active:scale-95'); } if (this.disabled) { interactionClasses.push('opacity-50 cursor-not-allowed'); } if (this.loading) { interactionClasses.push('opacity-70 cursor-wait'); } const classes = [ ...baseClasses, ...variantClasses[this.variant], ...interactionClasses ]; if (!this.fullWidth) { classes.push(sizeClasses[this.size]); } return classes.join(' '); } get headerClasses() { const sizeClasses = { sm: 'p-2 pb-1', md: 'p-3 pb-2', lg: 'p-4 pb-2' }; return [ 'border-b border-amber-100', sizeClasses[this.size] ].join(' '); } get contentClasses() { const sizeClasses = { sm: 'p-2', md: 'p-3', lg: 'p-4' }; const spacingClasses = []; if (this.hasHeader && !this.image) spacingClasses.push('pt-2'); if (this.hasFooter) spacingClasses.push('pb-2'); if (this.image && !this.hasHeader) spacingClasses.push('pt-0'); return [ sizeClasses[this.size], ...spacingClasses ].join(' '); } get footerClasses() { const sizeClasses = { sm: 'p-2 pt-1', md: 'p-3 pt-2', lg: 'p-4 pt-2' }; return [ 'border-t border-amber-100', sizeClasses[this.size] ].join(' '); } get imageClasses() { return [ 'w-full h-32 object-cover', 'transition-transform duration-200', this.clickable ? 'hover:scale-105' : '' ].join(' '); } get rippleColor() { return 'rgba(245, 158, 11, 0.2)'; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CupcakeCard, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: CupcakeCard, isStandalone: true, selector: "cupcake-card", inputs: { variant: "variant", size: "size", title: "title", subtitle: "subtitle", image: "image", imageAlt: "imageAlt", imageLoading: "imageLoading", imageBadge: "imageBadge", avatar: "avatar", avatarAlt: "avatarAlt", clickable: "clickable", disabled: "disabled", loading: "loading", headerAction: "headerAction", hasHeader: "hasHeader", hasFooter: "hasFooter", ariaLabel: "ariaLabel", fullWidth: "fullWidth" }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: ` <div [class]="cardClasses" [attr.role]="clickable ? 'button' : null" [attr.tabindex]="clickable ? 0 : null" [attr.aria-label]="ariaLabel" (click)="handleClick($event)" (keyup.enter)="handleClick($event)" (keyup.space)="handleClick($event)" matRipple [matRippleDisabled]="!clickable" [matRippleColor]="rippleColor"> <!-- Header --> <div *ngIf="hasHeader" [class]="headerClasses"> <div class="flex items-center justify-between"> <div class="flex items-center space-x-3"> <div *ngIf="avatar" class="flex-shrink-0"> <img [src]="avatar" [alt]="avatarAlt" class="w-10 h-10 rounded-full object-cover"> </div> <div> <h3 *ngIf="title" class="text-lg font-semibold text-amber-900">{{ title }}</h3> <p *ngIf="subtitle" class="text-sm text-amber-600">{{ subtitle }}</p> </div> </div> <div *ngIf="headerAction" class="flex-shrink-0"> <ng-content select="[slot=header-action]"></ng-content> </div> </div> </div> <!-- Image --> <div *ngIf="image" class="relative overflow-hidden"> <img [src]="image" [alt]="imageAlt" [class]="imageClasses" [loading]="imageLoading"> <div *ngIf="imageBadge" class="absolute top-2 right-2"> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800"> {{ imageBadge }} </span> </div> </div> <!-- Content --> <div [class]="contentClasses"> <ng-content></ng-content> </div> <!-- Footer --> <div *ngIf="hasFooter" [class]="footerClasses"> <ng-content select="[slot=footer]"></ng-content> </div> </div> `, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i2.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CupcakeCard, decorators: [{ type: Component, args: [{ selector: 'cupcake-card', standalone: true, imports: [CommonModule, MatCardModule, MatRippleModule], changeDetection: ChangeDetectionStrategy.OnPush, template: ` <div [class]="cardClasses" [attr.role]="clickable ? 'button' : null" [attr.tabindex]="clickable ? 0 : null" [attr.aria-label]="ariaLabel" (click)="handleClick($event)" (keyup.enter)="handleClick($event)" (keyup.space)="handleClick($event)" matRipple [matRippleDisabled]="!clickable" [matRippleColor]="rippleColor"> <!-- Header --> <div *ngIf="hasHeader" [class]="headerClasses"> <div class="flex items-center justify-between"> <div class="flex items-center space-x-3"> <div *ngIf="avatar" class="flex-shrink-0"> <img [src]="avatar" [alt]="avatarAlt" class="w-10 h-10 rounded-full object-cover"> </div> <div> <h3 *ngIf="title" class="text-lg font-semibold text-amber-900">{{ title }}</h3> <p *ngIf="subtitle" class="text-sm text-amber-600">{{ subtitle }}</p> </div> </div> <div *ngIf="headerAction" class="flex-shrink-0"> <ng-content select="[slot=header-action]"></ng-content> </div> </div> </div> <!-- Image --> <div *ngIf="image" class="relative overflow-hidden"> <img [src]="image" [alt]="imageAlt" [class]="imageClasses" [loading]="imageLoading"> <div *ngIf="imageBadge" class="absolute top-2 right-2"> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800"> {{ imageBadge }} </span> </div> </div> <!-- Content --> <div [class]="contentClasses"> <ng-content></ng-content> </div> <!-- Footer --> <div *ngIf="hasFooter" [class]="footerClasses"> <ng-content select="[slot=footer]"></ng-content> </div> </div> `, styles: [":host{display:block;width:100%}\n"] }] }], propDecorators: { variant: [{ type: Input }], size: [{ type: Input }], title: [{ type: Input }], subtitle: [{ type: Input }], image: [{ type: Input }], imageAlt: [{ type: Input }], imageLoading: [{ type: Input }], imageBadge: [{ type: Input }], avatar: [{ type: Input }], avatarAlt: [{ type: Input }], clickable: [{ type: Input }], disabled: [{ type: Input }], loading: [{ type: Input }], headerAction: [{ type: Input }], hasHeader: [{ type: Input }], hasFooter: [{ type: Input }], ariaLabel: [{ type: Input }], fullWidth: [{ type: Input }], cardClick: [{ type: Output }] } }); class ToastNotification { type = 'info'; title; message = ''; duration = 5000; // 5 seconds closable = true; actionText; position = 'top-right'; persistent = false; // Don't auto-hide visible = true; closed = new EventEmitter(); action = new EventEmitter(); timeoutId; ngOnInit() { if (!this.persistent && this.duration > 0) { this.timeoutId = window.setTimeout(() => { this.onClose(); }, this.duration); } } ngOnDestroy() { if (this.timeoutId) { clearTimeout(this.timeoutId); } } onClose() { this.visible = false; this.closed.emit(); } onAction() { this.action.emit(); } get toastClasses() { const baseClasses = [ 'max-w-xs w-full bg-white shadow-md rounded-md pointer-events-auto', 'ring-1 ring-black ring-opacity-5 overflow-hidden', 'transition-all duration-300 ease-in-out' ]; const typeClasses = { success: 'border-l-2 border-green-400', error: 'border-l-2 border-red-400', warning: 'border-l-2 border-yellow-400', info: 'border-l-2 border-blue-400' }; return [ ...baseClasses, typeClasses[this.type], 'p-2' ].join(' '); } get iconClasses() { const baseClasses = 'h-4 w-4'; const typeClasses = { success: 'text-green-400', error: 'text-red-400', warning: 'text-yellow-400', info: 'text-blue-400' }; return [baseClasses, typeClasses[this.type]].join(' '); } get titleClasses() { return 'text-xs font-medium text-gray-900'; } get messageClasses() { return this.title ? 'mt-0.5 text-xs text-gray-500' : 'text-xs text-gray-900'; } get actionButtonClasses() { const typeClasses = { success: 'text-green-600 hover:text-green-500 focus:ring-green-500', error: 'text-red-600 hover:text-red-500 focus:ring-red-500', warning: 'text-yellow-600 hover:text-yellow-500 focus:ring-yellow-500', info: 'text-blue-600 hover:text-blue-500 focus:ring-blue-500' }; return [ 'text-sm font-medium rounded-md', 'focus:outline-none focus:ring-2 focus:ring-offset-2', 'transition-colors duration-200', typeClasses[this.type] ].join(' '); } get closeButtonClasses() { return [ 'bg-white rounded-md inline-flex text-gray-400', 'hover:text-gray-500 focus:outline-none', 'focus:ring-2 focus:ring-offset-2 focus:ring-amber-500', 'transition-colors duration-200' ].join(' '); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ToastNotification, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ToastNotification, isStandalone: true, selector: "toast-notification", inputs: { type: "type", title: "title", message: "message", duration: "duration", closable: "closable", actionText: "actionText", position: "position", persistent: "persistent", visible: "visible" }, outputs: { closed: "closed", action: "action" }, ngImport: i0, template: ` <div *ngIf="visible" [@slideIn] [class]="toastClasses" [attr.role]="'alert'" [attr.aria-live]="'polite'" [attr.aria-atomic]="'true'"> <!-- Icon --> <div class="flex-shrink-0"> <svg [class]="iconClasses" fill="currentColor" viewBox="0 0 20 20"> <!-- Success Icon --> <path *ngIf="type === 'success'" fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /> <!-- Error Icon --> <path *ngIf="type === 'error'" fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /> <!-- Warning Icon --> <path *ngIf="type === 'warning'" fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" /> <!-- Info Icon --> <path *ngIf="type === 'info'" fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" /> </svg> </div> <!-- Content --> <div class="ml-2 flex-1"> <h4 *ngIf="title" [class]="titleClasses">{{ title }}</h4> <p [class]="messageClasses">{{ message }}</p> <!-- Action Button --> <div *ngIf="actionText" class="mt-1"> <button type="button" [class]="actionButtonClasses" (click)="onAction()"> {{ actionText }} </button> </div> </div> <!-- Close Button --> <div *ngIf="closable" class="ml-2 flex-shrink-0 flex"> <button type="button" [class]="closeButtonClasses" (click)="onClose()" [attr.aria-label]="'Close notification'"> <svg class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /> </svg> </button> </div> </div> `, isInline: true, styles: [":host{display:block;position:fixed;z-index:1000}:host(.top-right){top:1rem;right:1rem}:host(.top-left){top:1rem;left:1rem}:host(.bottom-right){bottom:1rem;right:1rem}:host(.bottom-left){bottom:1rem;left:1rem}:host(.top-center){top:1rem;left:50%;transform:translate(-50%)}:host(.bottom-center){bottom:1rem;left:50%;transform:translate(-50%)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatSnackBarModule }], animations: [ trigger('slideIn', [ state('in', style({ transform: 'translateX(0)', opacity: 1 })), transition('void => *', [ style({ transform: 'translateX(100%)', opacity: 0 }), animate('300ms ease-in-out') ]), transition('* => void', [ animate('300ms ease-in-out', style({ transform: 'translateX(100%)', opacity: 0 })) ]) ]) ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ToastNotification, decorators: [{ type: Component, args: [{ selector: 'toast-notification', standalone: true, imports: [CommonModule, MatSnackBarModule], changeDetection: ChangeDetectionStrategy.OnPush, animations: [ trigger('slideIn', [ state('in', style({ transform: 'translateX(0)', opacity: 1 })), transition('void => *', [ style({ transform: 'translateX(100%)', opacity: 0 }), animate('300ms ease-in-out') ]), transition('* => void', [ animate('300ms ease-in-out', style({ transform: 'translateX(100%)', opacity: 0 })) ]) ]) ], template: ` <div *ngIf="visible" [@slideIn] [class]="toastClasses" [attr.role]="'alert'" [attr.aria-live]="'polite'" [attr.aria-atomic]="'true'"> <!-- Icon --> <div class="flex-shrink-0"> <svg [class]="iconClasses" fill="currentColor" viewBox="0 0 20 20"> <!-- Success Icon --> <path *ngIf="type === 'success'" fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /> <!-- Error Icon --> <path *ngIf="type === 'error'" fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /> <!-- Warning Icon --> <path *ngIf="type === 'warning'" fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" /> <!-- Info Icon --> <path *ngIf="type === 'info'" fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" /> </svg> </div> <!-- Content --> <div class="ml-2 flex-1"> <h4 *ngIf="title" [class]="titleClasses">{{ title }}</h4> <p [class]="messageClasses">{{ message }}</p> <!-- Action Button --> <div *ngIf="actionText" class="mt-1"> <button type="button" [class]="actionButtonClasses" (click)="onAction()"> {{ actionText }} </button> </div> </div> <!-- Close Button --> <div *ngIf="closable" class="ml-2 flex-shrink-0 flex"> <button type="button" [class]="closeButtonClasses" (click)="onClose()" [attr.aria-label]="'Close notification'"> <svg class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /> </svg> </button> </div> </div> `, styles: [":host{display:block;position:fixed;z-index:1000}:host(.top-right){top:1rem;right:1rem}:host(.top-left){top:1rem;left:1rem}:host(.bottom-right){bottom:1rem;right:1rem}:host(.bottom-left){bottom:1rem;left:1rem}:host(.top-center){top:1rem;left:50%;transform:translate(-50%)}:host(.bottom-center){bottom:1rem;left:50%;transform:translate(-50%)}\n"] }] }], propDecorators: { type: [{ type: Input }], title: [{ type: Input }], message: [{ type: Input }], duration: [{ type: Input }], closable: [{ type: Input }], actionText: [{ type: Input }], position: [{ type: Input }], persistent: [{ type: Input }], visible: [{ type: Input }], closed: [{ type: Output }], action: [{ type: Output }] } }); /* * Public API Surface of bakery-ui */ /** * Generated bundle index. Do not edit. */ export { BagelInput, BaguetteButton, BakeryUi, CupcakeCard, ToastNotification }; //# sourceMappingURL=bakery-ui.mjs.map