ngx-com 0.0.3 → 0.0.5

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.
Files changed (33) hide show
  1. package/fesm2022/ngx-com-components-avatar.mjs +772 -0
  2. package/fesm2022/ngx-com-components-avatar.mjs.map +1 -0
  3. package/fesm2022/ngx-com-components-calendar.mjs +33 -130
  4. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -1
  5. package/fesm2022/ngx-com-components-confirm.mjs +562 -0
  6. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -0
  7. package/fesm2022/ngx-com-components-dropdown.mjs +119 -25
  8. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -1
  9. package/fesm2022/ngx-com-components-empty-state.mjs +382 -0
  10. package/fesm2022/ngx-com-components-empty-state.mjs.map +1 -0
  11. package/fesm2022/ngx-com-components-form-field.mjs +16 -15
  12. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -1
  13. package/fesm2022/ngx-com-components-item.mjs +578 -0
  14. package/fesm2022/ngx-com-components-item.mjs.map +1 -0
  15. package/fesm2022/ngx-com-components-paginator.mjs +823 -0
  16. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -0
  17. package/fesm2022/ngx-com-components-segmented-control.mjs +538 -0
  18. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -0
  19. package/fesm2022/ngx-com-components-spinner.mjs +189 -0
  20. package/fesm2022/ngx-com-components-spinner.mjs.map +1 -0
  21. package/fesm2022/ngx-com-components-tooltip.mjs +625 -0
  22. package/fesm2022/ngx-com-components-tooltip.mjs.map +1 -0
  23. package/package.json +33 -1
  24. package/types/ngx-com-components-avatar.d.ts +409 -0
  25. package/types/ngx-com-components-calendar.d.ts +5 -0
  26. package/types/ngx-com-components-confirm.d.ts +160 -0
  27. package/types/ngx-com-components-dropdown.d.ts +52 -28
  28. package/types/ngx-com-components-empty-state.d.ts +269 -0
  29. package/types/ngx-com-components-item.d.ts +336 -0
  30. package/types/ngx-com-components-paginator.d.ts +265 -0
  31. package/types/ngx-com-components-segmented-control.d.ts +274 -0
  32. package/types/ngx-com-components-spinner.d.ts +120 -0
  33. package/types/ngx-com-components-tooltip.d.ts +200 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-com-components-segmented-control.mjs","sources":["../../../projects/com/components/segmented-control/segment-def.directive.ts","../../../projects/com/components/segmented-control/segmented-control.variants.ts","../../../projects/com/components/segmented-control/segmented-control.component.ts","../../../projects/com/components/segmented-control/index.ts","../../../projects/com/components/segmented-control/ngx-com-components-segmented-control.ts"],"sourcesContent":["import { Directive, TemplateRef, inject } from '@angular/core';\nimport type { SegmentOption } from './segmented-control.component';\n\n/**\n * Template context provided to custom segment templates.\n *\n * @example\n * ```html\n * <ng-template comSegmentDef let-option let-active=\"active\">\n * <com-icon [name]=\"option.value\" />\n * <span>{{ option.label }}</span>\n * </ng-template>\n * ```\n */\nexport interface SegmentTemplateContext<T = unknown> {\n /** The option object (default for `let-option`). */\n $implicit: SegmentOption<T>;\n /** Whether this segment is currently selected. */\n active: boolean;\n /** Whether this segment is disabled. */\n disabled: boolean;\n /** Position in the options list (0-indexed). */\n index: number;\n}\n\n/**\n * Directive to mark a custom template for segment content.\n *\n * The template receives a `SegmentTemplateContext` with the option data,\n * active state, disabled state, and index. Use this to customize the\n * inner content of each segment while the component manages the button,\n * styling, and ARIA attributes.\n *\n * @example Icon + text\n * ```html\n * <com-segmented-control [options]=\"viewOptions\" [(value)]=\"currentView\">\n * <ng-template comSegmentDef let-option let-active=\"active\">\n * <com-icon [name]=\"option.value\" size=\"sm\" />\n * <span>{{ option.label }}</span>\n * </ng-template>\n * </com-segmented-control>\n * ```\n *\n * @example Icon only (label used for accessibility)\n * ```html\n * <com-segmented-control [options]=\"alignmentOptions\" [(value)]=\"alignment\">\n * <ng-template comSegmentDef let-option>\n * <com-icon [name]=\"'align-' + option.value\" size=\"sm\" />\n * </ng-template>\n * </com-segmented-control>\n * ```\n */\n@Directive({\n selector: 'ng-template[comSegmentDef]',\n})\nexport class ComSegmentDef<T = unknown> {\n readonly templateRef: TemplateRef<SegmentTemplateContext<T>> = inject(TemplateRef);\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\n\n// ─── Types ───\n\nexport type SegmentedControlSize = 'sm' | 'md' | 'lg';\nexport type SegmentedControlColor = 'primary' | 'accent' | 'muted';\nexport type SegmentedControlVariant = 'filled' | 'outline';\n\n// ─── Container Variants ───\n\n/**\n * CVA variants for the segmented control container (track).\n * Controls overall sizing, padding, and track background.\n */\nexport const segmentedControlContainerVariants: (props?: {\n size?: SegmentedControlSize;\n fullWidth?: boolean;\n}) => string = cva(\n [\n 'inline-flex items-center',\n 'rounded-pill',\n 'bg-muted',\n 'p-1',\n 'transition-colors duration-150',\n ],\n {\n variants: {\n size: {\n sm: 'gap-0.5',\n md: 'gap-1',\n lg: 'gap-1',\n },\n fullWidth: {\n true: 'w-full',\n false: '',\n },\n },\n defaultVariants: {\n size: 'md',\n fullWidth: false,\n },\n }\n);\n\n// ─── Segment Variants ───\n\n/**\n * CVA variants for individual segment buttons.\n * Controls per-segment sizing, typography, and active/inactive color states.\n */\nexport const segmentedControlSegmentVariants: (props?: {\n size?: SegmentedControlSize;\n color?: SegmentedControlColor;\n variant?: SegmentedControlVariant;\n active?: boolean;\n fullWidth?: boolean;\n}) => string = cva(\n [\n 'inline-flex items-center justify-center',\n 'rounded-pill',\n 'font-medium whitespace-nowrap select-none',\n 'transition-colors duration-150',\n 'cursor-pointer',\n 'gap-1.5',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n ],\n {\n variants: {\n size: {\n sm: 'h-7 px-3 text-sm',\n md: 'h-8 px-4 text-sm',\n lg: 'h-10 px-5 text-base',\n },\n color: {\n primary: '',\n accent: '',\n muted: '',\n },\n variant: {\n filled: '',\n outline: 'bg-transparent',\n },\n active: {\n true: '',\n false: 'text-foreground',\n },\n fullWidth: {\n true: 'flex-1',\n false: '',\n },\n },\n compoundVariants: [\n // ─── Filled + Active ───\n {\n variant: 'filled',\n color: 'primary',\n active: true,\n class: 'bg-primary text-primary-foreground shadow-sm',\n },\n {\n variant: 'filled',\n color: 'accent',\n active: true,\n class: 'bg-accent text-accent-foreground shadow-sm',\n },\n {\n variant: 'filled',\n color: 'muted',\n active: true,\n class: 'bg-background text-foreground shadow-sm',\n },\n\n // ─── Filled + Inactive (hover) ───\n {\n variant: 'filled',\n active: false,\n class: 'hover:bg-muted-hover',\n },\n\n // ─── Outline + Active ───\n {\n variant: 'outline',\n color: 'primary',\n active: true,\n class: 'ring-2 ring-primary text-primary',\n },\n {\n variant: 'outline',\n color: 'accent',\n active: true,\n class: 'ring-2 ring-accent text-accent',\n },\n {\n variant: 'outline',\n color: 'muted',\n active: true,\n class: 'ring-2 ring-border text-foreground',\n },\n\n // ─── Outline + Inactive (hover) ───\n {\n variant: 'outline',\n active: false,\n class: 'hover:bg-muted-hover',\n },\n ],\n defaultVariants: {\n size: 'md',\n color: 'primary',\n variant: 'filled',\n active: false,\n fullWidth: false,\n },\n }\n);\n\n// ─── Disabled Segment Classes ───\n\n/**\n * Classes to apply when a segment is disabled.\n * Separate from CVA to avoid complex variant combinations.\n */\nexport const SEGMENT_DISABLED_CLASSES =\n 'bg-disabled text-disabled-foreground cursor-not-allowed hover:bg-disabled';\n\n// ─── Variant Types ───\n\nexport type SegmentedControlContainerVariants = VariantProps<typeof segmentedControlContainerVariants>;\nexport type SegmentedControlSegmentVariants = VariantProps<typeof segmentedControlSegmentVariants>;\n","import {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n computed,\n contentChild,\n ElementRef,\n input,\n model,\n viewChildren,\n ViewEncapsulation,\n} from '@angular/core';\nimport type { InputSignal, InputSignalWithTransform, ModelSignal, Signal } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { mergeClasses } from 'ngx-com/utils';\nimport { ComSegmentDef, type SegmentTemplateContext } from './segment-def.directive';\nimport {\n segmentedControlContainerVariants,\n segmentedControlSegmentVariants,\n SEGMENT_DISABLED_CLASSES,\n} from './segmented-control.variants';\nimport type {\n SegmentedControlSize,\n SegmentedControlColor,\n SegmentedControlVariant,\n} from './segmented-control.variants';\n\n/**\n * Represents a single option in the segmented control.\n */\nexport interface SegmentOption<T = unknown> {\n /** The value associated with this option. */\n value: T;\n /** Display label (also used as aria-label fallback for custom templates). */\n label: string;\n /** Whether this option is disabled. */\n disabled?: boolean | undefined;\n}\n\n/**\n * Segmented control component — a horizontal group of mutually exclusive options\n * where one is always selected. Think of it as a styled radio group in pill form.\n *\n * Supports two rendering modes:\n * - **Simple mode**: Plain text labels from the `label` property\n * - **Custom template mode**: Full control via `ng-template[comSegmentDef]`\n *\n * @tokens `--color-primary`, `--color-primary-foreground`,\n * `--color-accent`, `--color-accent-foreground`,\n * `--color-muted`, `--color-muted-foreground`, `--color-muted-hover`,\n * `--color-background`, `--color-foreground`,\n * `--color-disabled`, `--color-disabled-foreground`,\n * `--color-border`, `--color-ring`\n *\n * @example Basic two-option toggle\n * ```html\n * <com-segmented-control\n * [options]=\"[\n * { value: 'admin', label: 'Admin' },\n * { value: 'user', label: 'User' }\n * ]\"\n * [(value)]=\"selectedRole\"\n * />\n * ```\n *\n * @example Multiple options with variants\n * ```html\n * <com-segmented-control\n * [options]=\"viewOptions\"\n * [(value)]=\"currentView\"\n * color=\"primary\"\n * size=\"sm\"\n * />\n * ```\n *\n * @example Custom template with icons\n * ```html\n * <com-segmented-control [options]=\"viewOptions\" [(value)]=\"currentView\" color=\"primary\">\n * <ng-template comSegmentDef let-option let-active=\"active\">\n * <com-icon [name]=\"option.value === 'grid' ? 'grid' : 'list'\" size=\"sm\" />\n * <span>{{ option.label }}</span>\n * </ng-template>\n * </com-segmented-control>\n * ```\n *\n * @example Custom template with badges\n * ```html\n * <com-segmented-control [options]=\"statusOptions\" [(value)]=\"statusFilter\" color=\"accent\">\n * <ng-template comSegmentDef let-option let-active=\"active\">\n * <span>{{ option.label }}</span>\n * <span\n * class=\"ml-1.5 rounded-pill px-1.5 text-xs\"\n * [class]=\"active ? 'bg-accent-foreground/20 text-accent-foreground' : 'bg-muted text-muted-foreground'\"\n * >\n * {{ option.value === 'open' ? openCount : closedCount }}\n * </span>\n * </ng-template>\n * </com-segmented-control>\n * ```\n *\n * @example Icon only (label used for accessibility)\n * ```html\n * <com-segmented-control\n * [options]=\"[\n * { value: 'left', label: 'Align left' },\n * { value: 'center', label: 'Align center' },\n * { value: 'right', label: 'Align right' }\n * ]\"\n * [(value)]=\"alignment\"\n * size=\"sm\"\n * >\n * <ng-template comSegmentDef let-option>\n * <com-icon [name]=\"'align-' + option.value\" size=\"sm\" />\n * </ng-template>\n * </com-segmented-control>\n * ```\n *\n * @example Full width + outline variant\n * ```html\n * <com-segmented-control\n * [options]=\"plans\"\n * [(value)]=\"selectedPlan\"\n * variant=\"outline\"\n * color=\"primary\"\n * [fullWidth]=\"true\"\n * />\n * ```\n *\n * @example Disabled options\n * ```html\n * <com-segmented-control\n * [options]=\"[\n * { value: 'free', label: 'Free' },\n * { value: 'pro', label: 'Pro' },\n * { value: 'enterprise', label: 'Enterprise', disabled: true }\n * ]\"\n * [(value)]=\"plan\"\n * color=\"primary\"\n * size=\"lg\"\n * />\n * ```\n */\n@Component({\n selector: 'com-segmented-control',\n exportAs: 'comSegmentedControl',\n template: `\n <div\n role=\"radiogroup\"\n [attr.aria-label]=\"ariaLabel()\"\n [class]=\"containerClasses()\"\n >\n @for (option of options(); track option.value; let i = $index) {\n <button\n #segmentButton\n type=\"button\"\n role=\"radio\"\n [attr.aria-checked]=\"isActive(option)\"\n [attr.aria-label]=\"customTemplate() ? option.label : null\"\n [attr.aria-disabled]=\"option.disabled || null\"\n [disabled]=\"option.disabled\"\n [tabindex]=\"tabIndexFor(i)\"\n [class]=\"segmentClasses(option)\"\n (click)=\"select(option)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (customTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl.templateRef\"\n [ngTemplateOutletContext]=\"getTemplateContext(option, i)\"\n />\n } @else {\n {{ option.label }}\n }\n </button>\n }\n </div>\n `,\n styles: `\n com-segmented-control {\n display: inline-block;\n }\n com-segmented-control[fullWidth] {\n display: block;\n }\n `,\n imports: [NgTemplateOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'com-segmented-control',\n '[attr.fullWidth]': 'fullWidth() || null',\n },\n})\nexport class ComSegmentedControl<T = unknown> {\n // ─── View Children ───\n\n /** References to all segment buttons for focus management. */\n private readonly segmentButtons: Signal<readonly ElementRef<HTMLButtonElement>[]> =\n viewChildren<ElementRef<HTMLButtonElement>>('segmentButton');\n\n // ─── Inputs ───\n\n /** The list of options to display. */\n readonly options: InputSignal<SegmentOption<T>[]> = input.required<SegmentOption<T>[]>();\n\n /** Currently selected value. Two-way bindable with `[(value)]`. */\n readonly value: ModelSignal<T | undefined> = model<T | undefined>(undefined);\n\n /** Controls segment height, padding, and font size. */\n readonly size: InputSignal<SegmentedControlSize> = input<SegmentedControlSize>('md');\n\n /** Color scheme for the active segment. */\n readonly color: InputSignal<SegmentedControlColor> = input<SegmentedControlColor>('primary');\n\n /** Visual variant: filled (solid background) or outline (ring border). */\n readonly variant: InputSignal<SegmentedControlVariant> = input<SegmentedControlVariant>('filled');\n\n /** When true, segments stretch equally to fill available width. */\n readonly fullWidth: InputSignalWithTransform<boolean, unknown> = input(false, {\n transform: booleanAttribute,\n });\n\n /** Accessible label for the radiogroup container. */\n readonly ariaLabel: InputSignal<string | null> = input<string | null>(null, {\n alias: 'aria-label',\n });\n\n /** Custom CSS classes to merge with container classes. */\n readonly userClass: InputSignal<string> = input('', { alias: 'class' });\n\n // ─── Content Child ───\n\n /** Optional custom template for segment content. */\n readonly customTemplate: Signal<ComSegmentDef<T> | undefined> = contentChild<ComSegmentDef<T>>(ComSegmentDef);\n\n // ─── Computed ───\n\n /** Classes for the container/track element. */\n protected readonly containerClasses: Signal<string> = computed(() =>\n mergeClasses(\n segmentedControlContainerVariants({\n size: this.size(),\n fullWidth: this.fullWidth(),\n }),\n this.userClass()\n )\n );\n\n // ─── Public Methods ───\n\n /**\n * Checks if the given option is currently selected.\n */\n isActive(option: SegmentOption<T>): boolean {\n return this.value() === option.value;\n }\n\n /**\n * Selects the given option (if not disabled).\n */\n select(option: SegmentOption<T>): void {\n if (option.disabled) {\n return;\n }\n this.value.set(option.value);\n }\n\n /**\n * Returns the tabindex for a segment at the given index.\n * Implements roving tabindex: only the selected (or first focusable) segment has tabindex=\"0\".\n */\n tabIndexFor(index: number): number {\n const opts = this.options();\n const currentValue = this.value();\n\n // If current value matches this option, it gets tabindex=\"0\"\n if (opts[index]?.value === currentValue) {\n return 0;\n }\n\n // If no value is selected, first non-disabled option gets tabindex=\"0\"\n if (currentValue === undefined) {\n const firstFocusableIndex = opts.findIndex((o) => !o.disabled);\n return index === firstFocusableIndex ? 0 : -1;\n }\n\n return -1;\n }\n\n /**\n * Returns computed classes for a segment button.\n */\n segmentClasses(option: SegmentOption<T>): string {\n const active = this.isActive(option);\n const baseClasses = segmentedControlSegmentVariants({\n size: this.size(),\n color: this.color(),\n variant: this.variant(),\n active,\n fullWidth: this.fullWidth(),\n });\n\n if (option.disabled) {\n return mergeClasses(baseClasses, SEGMENT_DISABLED_CLASSES);\n }\n\n return baseClasses;\n }\n\n /**\n * Builds the template context for custom templates.\n */\n getTemplateContext(option: SegmentOption<T>, index: number): SegmentTemplateContext<T> {\n return {\n $implicit: option,\n active: this.isActive(option),\n disabled: !!option.disabled,\n index,\n };\n }\n\n /**\n * Handles keyboard navigation for the segmented control.\n * Implements ARIA radiogroup keyboard patterns.\n */\n onKeydown(event: KeyboardEvent, currentIndex: number): void {\n const opts = this.options();\n let targetIndex: number | null = null;\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n targetIndex = this.findNextFocusableIndex(currentIndex, 1);\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n targetIndex = this.findNextFocusableIndex(currentIndex, -1);\n break;\n case 'Home':\n targetIndex = this.findFirstFocusableIndex();\n break;\n case 'End':\n targetIndex = this.findLastFocusableIndex();\n break;\n default:\n return;\n }\n\n if (targetIndex !== null && targetIndex !== currentIndex) {\n event.preventDefault();\n const targetOption = opts[targetIndex];\n if (targetOption) {\n this.select(targetOption);\n this.focusSegmentAt(targetIndex);\n }\n }\n }\n\n // ─── Private Methods ───\n\n /**\n * Finds the next focusable (non-disabled) option index in the given direction.\n * Wraps around to the beginning/end of the list.\n */\n private findNextFocusableIndex(currentIndex: number, direction: 1 | -1): number | null {\n const opts = this.options();\n const length = opts.length;\n\n if (length === 0) {\n return null;\n }\n\n let index = currentIndex;\n for (let i = 0; i < length; i++) {\n index = (index + direction + length) % length;\n if (!opts[index]?.disabled) {\n return index;\n }\n }\n\n return null;\n }\n\n /**\n * Finds the first focusable (non-disabled) option index.\n */\n private findFirstFocusableIndex(): number | null {\n const opts = this.options();\n const index = opts.findIndex((o) => !o.disabled);\n return index >= 0 ? index : null;\n }\n\n /**\n * Finds the last focusable (non-disabled) option index.\n */\n private findLastFocusableIndex(): number | null {\n const opts = this.options();\n for (let i = opts.length - 1; i >= 0; i--) {\n if (!opts[i]?.disabled) {\n return i;\n }\n }\n return null;\n }\n\n /**\n * Focuses the segment button at the given index.\n */\n private focusSegmentAt(index: number): void {\n const button = this.segmentButtons()[index];\n if (button) {\n button.nativeElement.focus();\n }\n }\n}\n","// Public API for the segmented-control component\n\n// Main component\nexport { ComSegmentedControl } from './segmented-control.component';\nexport type { SegmentOption } from './segmented-control.component';\n\n// Directive\nexport { ComSegmentDef } from './segment-def.directive';\nexport type { SegmentTemplateContext } from './segment-def.directive';\n\n// Variants\nexport {\n segmentedControlContainerVariants,\n segmentedControlSegmentVariants,\n SEGMENT_DISABLED_CLASSES,\n} from './segmented-control.variants';\n\nexport type {\n SegmentedControlSize,\n SegmentedControlColor,\n SegmentedControlVariant,\n SegmentedControlContainerVariants,\n SegmentedControlSegmentVariants,\n} from './segmented-control.variants';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,189 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { cva } from 'class-variance-authority';
4
+
5
+ /**
6
+ * CVA variants for the spinning circle element.
7
+ *
8
+ * Creates a circular track with a faded ring and a colored arc that rotates.
9
+ * Uses `border` for the track and `border-t-*` for the spinning arc.
10
+ *
11
+ * @tokens `--color-primary`, `--color-accent`, `--color-warn`, `--color-success`, `--color-muted-foreground`
12
+ */
13
+ const spinnerVariants = cva([
14
+ 'com-spinner__circle',
15
+ 'inline-block shrink-0 rounded-full border-solid',
16
+ 'animate-[com-spin_0.6s_linear_infinite]',
17
+ ], {
18
+ variants: {
19
+ size: {
20
+ xs: 'size-3 border-[1.5px]',
21
+ sm: 'size-4 border-2',
22
+ md: 'size-5 border-2',
23
+ lg: 'size-8 border-[2.5px]',
24
+ xl: 'size-10 border-3',
25
+ },
26
+ color: {
27
+ current: 'border-current/20 border-t-current',
28
+ primary: 'border-primary/20 border-t-primary',
29
+ accent: 'border-accent/20 border-t-accent',
30
+ warn: 'border-warn/20 border-t-warn',
31
+ success: 'border-success/20 border-t-success',
32
+ muted: 'border-muted-foreground/20 border-t-muted-foreground',
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ size: 'md',
37
+ color: 'current',
38
+ },
39
+ });
40
+ /**
41
+ * CVA variants for the spinner container layout.
42
+ *
43
+ * Controls flex direction and gap based on label position.
44
+ * Only applies when a visible label is present.
45
+ *
46
+ * @tokens none (layout only)
47
+ */
48
+ const spinnerContainerVariants = cva(['inline-flex items-center justify-center'], {
49
+ variants: {
50
+ labelPosition: {
51
+ right: 'flex-row gap-2',
52
+ bottom: 'flex-col gap-1.5',
53
+ },
54
+ },
55
+ defaultVariants: {
56
+ labelPosition: 'right',
57
+ },
58
+ });
59
+ /** Label font size classes mapped to spinner size. */
60
+ const SPINNER_LABEL_SIZES = {
61
+ xs: 'text-xs',
62
+ sm: 'text-xs',
63
+ md: 'text-sm',
64
+ lg: 'text-sm',
65
+ xl: 'text-base',
66
+ };
67
+
68
+ /**
69
+ * A minimal, CSS-only loading indicator.
70
+ *
71
+ * Drop-in anywhere: inside buttons, next to inline text, centered in cards,
72
+ * or as a page-level overlay. The animation is fast (0.6s) for a snappy feel.
73
+ *
74
+ * @tokens `--color-primary`, `--color-accent`, `--color-warn`, `--color-success`, `--color-muted-foreground`
75
+ *
76
+ * @example Simplest usage (inherits parent text color)
77
+ * ```html
78
+ * <com-spinner />
79
+ * ```
80
+ *
81
+ * @example Inside a button (inline, inherits button text color)
82
+ * ```html
83
+ * <button class="btn-primary" [disabled]="saving">
84
+ * @if (saving) {
85
+ * <com-spinner size="xs" />
86
+ * }
87
+ * Save
88
+ * </button>
89
+ * ```
90
+ *
91
+ * @example With visible label
92
+ * ```html
93
+ * <com-spinner label="Loading results..." />
94
+ * ```
95
+ *
96
+ * @example Centered in a card
97
+ * ```html
98
+ * <div class="flex items-center justify-center p-8">
99
+ * <com-spinner size="lg" color="primary" label="Loading..." labelPosition="bottom" />
100
+ * </div>
101
+ * ```
102
+ *
103
+ * @example Inline with text
104
+ * ```html
105
+ * <p class="text-muted-foreground">
106
+ * <com-spinner size="xs" /> Checking availability...
107
+ * </p>
108
+ * ```
109
+ *
110
+ * @example Color variants
111
+ * ```html
112
+ * <com-spinner color="primary" />
113
+ * <com-spinner color="accent" />
114
+ * <com-spinner color="warn" />
115
+ * ```
116
+ *
117
+ * @example Large page-level
118
+ * ```html
119
+ * <com-spinner size="xl" color="primary" label="Preparing your dashboard..." labelPosition="bottom" />
120
+ * ```
121
+ */
122
+ class ComSpinner {
123
+ /**
124
+ * Optional loading text (e.g., "Loading...", "Saving...").
125
+ * When omitted, "Loading" is rendered for screen readers only (sr-only).
126
+ * When provided, the label is visible next to or below the spinner.
127
+ */
128
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : []));
129
+ /**
130
+ * Position of the label relative to the spinner.
131
+ * Only relevant when a visible `label` is provided.
132
+ */
133
+ labelPosition = input('right', ...(ngDevMode ? [{ debugName: "labelPosition" }] : []));
134
+ /** Size of the spinner. */
135
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
136
+ /**
137
+ * Color of the spinner.
138
+ * `current` inherits from `currentColor` to match surrounding text.
139
+ */
140
+ color = input('current', ...(ngDevMode ? [{ debugName: "color" }] : []));
141
+ /** @internal Computed classes for the spinning circle. */
142
+ spinnerClasses = computed(() => spinnerVariants({
143
+ size: this.size(),
144
+ color: this.color(),
145
+ }), ...(ngDevMode ? [{ debugName: "spinnerClasses" }] : []));
146
+ /** @internal Computed classes for the container/host. */
147
+ containerClasses = computed(() => this.label()
148
+ ? spinnerContainerVariants({ labelPosition: this.labelPosition() })
149
+ : spinnerContainerVariants(), ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
150
+ /** @internal Computed label font size class. */
151
+ labelSizeClass = computed(() => SPINNER_LABEL_SIZES[this.size()], ...(ngDevMode ? [{ debugName: "labelSizeClass" }] : []));
152
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSpinner, deps: [], target: i0.ɵɵFactoryTarget.Component });
153
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComSpinner, isStandalone: true, selector: "com-spinner", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", 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 } }, host: { attributes: { "role": "status" }, properties: { "class": "containerClasses()" } }, exportAs: ["comSpinner"], ngImport: i0, template: `
154
+ <span [class]="spinnerClasses()" aria-hidden="true"></span>
155
+ @if (label()) {
156
+ <span class="select-none text-muted-foreground" [class]="labelSizeClass()">
157
+ {{ label() }}
158
+ </span>
159
+ } @else {
160
+ <span class="sr-only">Loading</span>
161
+ }
162
+ `, isInline: true, styles: ["@keyframes com-spin{to{transform:rotate(360deg)}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
163
+ }
164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSpinner, decorators: [{
165
+ type: Component,
166
+ args: [{ selector: 'com-spinner', exportAs: 'comSpinner', template: `
167
+ <span [class]="spinnerClasses()" aria-hidden="true"></span>
168
+ @if (label()) {
169
+ <span class="select-none text-muted-foreground" [class]="labelSizeClass()">
170
+ {{ label() }}
171
+ </span>
172
+ } @else {
173
+ <span class="sr-only">Loading</span>
174
+ }
175
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
176
+ role: 'status',
177
+ '[class]': 'containerClasses()',
178
+ }, styles: ["@keyframes com-spin{to{transform:rotate(360deg)}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
179
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }] } });
180
+
181
+ // Public API for the spinner component
182
+ // Main component
183
+
184
+ /**
185
+ * Generated bundle index. Do not edit.
186
+ */
187
+
188
+ export { ComSpinner, SPINNER_LABEL_SIZES, spinnerContainerVariants, spinnerVariants };
189
+ //# sourceMappingURL=ngx-com-components-spinner.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-com-components-spinner.mjs","sources":["../../../projects/com/components/spinner/spinner.variants.ts","../../../projects/com/components/spinner/spinner.component.ts","../../../projects/com/components/spinner/index.ts","../../../projects/com/components/spinner/ngx-com-components-spinner.ts"],"sourcesContent":["import { cva } from 'class-variance-authority';\n\n/** Spinner size variants. */\nexport type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** Spinner color variants. */\nexport type SpinnerColor = 'current' | 'primary' | 'accent' | 'warn' | 'success' | 'muted';\n\n/** Label position relative to spinner. */\nexport type SpinnerLabelPosition = 'right' | 'bottom';\n\n/**\n * CVA variants for the spinning circle element.\n *\n * Creates a circular track with a faded ring and a colored arc that rotates.\n * Uses `border` for the track and `border-t-*` for the spinning arc.\n *\n * @tokens `--color-primary`, `--color-accent`, `--color-warn`, `--color-success`, `--color-muted-foreground`\n */\nexport const spinnerVariants: (props?: {\n size?: SpinnerSize;\n color?: SpinnerColor;\n}) => string = cva(\n [\n 'com-spinner__circle',\n 'inline-block shrink-0 rounded-full border-solid',\n 'animate-[com-spin_0.6s_linear_infinite]',\n ],\n {\n variants: {\n size: {\n xs: 'size-3 border-[1.5px]',\n sm: 'size-4 border-2',\n md: 'size-5 border-2',\n lg: 'size-8 border-[2.5px]',\n xl: 'size-10 border-3',\n },\n color: {\n current: 'border-current/20 border-t-current',\n primary: 'border-primary/20 border-t-primary',\n accent: 'border-accent/20 border-t-accent',\n warn: 'border-warn/20 border-t-warn',\n success: 'border-success/20 border-t-success',\n muted: 'border-muted-foreground/20 border-t-muted-foreground',\n },\n },\n defaultVariants: {\n size: 'md',\n color: 'current',\n },\n }\n);\n\n/**\n * CVA variants for the spinner container layout.\n *\n * Controls flex direction and gap based on label position.\n * Only applies when a visible label is present.\n *\n * @tokens none (layout only)\n */\nexport const spinnerContainerVariants: (props?: {\n labelPosition?: SpinnerLabelPosition;\n}) => string = cva(\n ['inline-flex items-center justify-center'],\n {\n variants: {\n labelPosition: {\n right: 'flex-row gap-2',\n bottom: 'flex-col gap-1.5',\n },\n },\n defaultVariants: {\n labelPosition: 'right',\n },\n }\n);\n\n/** Label font size classes mapped to spinner size. */\nexport const SPINNER_LABEL_SIZES: Record<SpinnerSize, string> = {\n xs: 'text-xs',\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-sm',\n xl: 'text-base',\n};\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n input,\n ViewEncapsulation,\n} from '@angular/core';\nimport type { InputSignal, Signal } from '@angular/core';\nimport {\n spinnerVariants,\n spinnerContainerVariants,\n SPINNER_LABEL_SIZES,\n} from './spinner.variants';\nimport type {\n SpinnerSize,\n SpinnerColor,\n SpinnerLabelPosition,\n} from './spinner.variants';\n\n/**\n * A minimal, CSS-only loading indicator.\n *\n * Drop-in anywhere: inside buttons, next to inline text, centered in cards,\n * or as a page-level overlay. The animation is fast (0.6s) for a snappy feel.\n *\n * @tokens `--color-primary`, `--color-accent`, `--color-warn`, `--color-success`, `--color-muted-foreground`\n *\n * @example Simplest usage (inherits parent text color)\n * ```html\n * <com-spinner />\n * ```\n *\n * @example Inside a button (inline, inherits button text color)\n * ```html\n * <button class=\"btn-primary\" [disabled]=\"saving\">\n * @if (saving) {\n * <com-spinner size=\"xs\" />\n * }\n * Save\n * </button>\n * ```\n *\n * @example With visible label\n * ```html\n * <com-spinner label=\"Loading results...\" />\n * ```\n *\n * @example Centered in a card\n * ```html\n * <div class=\"flex items-center justify-center p-8\">\n * <com-spinner size=\"lg\" color=\"primary\" label=\"Loading...\" labelPosition=\"bottom\" />\n * </div>\n * ```\n *\n * @example Inline with text\n * ```html\n * <p class=\"text-muted-foreground\">\n * <com-spinner size=\"xs\" /> Checking availability...\n * </p>\n * ```\n *\n * @example Color variants\n * ```html\n * <com-spinner color=\"primary\" />\n * <com-spinner color=\"accent\" />\n * <com-spinner color=\"warn\" />\n * ```\n *\n * @example Large page-level\n * ```html\n * <com-spinner size=\"xl\" color=\"primary\" label=\"Preparing your dashboard...\" labelPosition=\"bottom\" />\n * ```\n */\n@Component({\n selector: 'com-spinner',\n exportAs: 'comSpinner',\n template: `\n <span [class]=\"spinnerClasses()\" aria-hidden=\"true\"></span>\n @if (label()) {\n <span class=\"select-none text-muted-foreground\" [class]=\"labelSizeClass()\">\n {{ label() }}\n </span>\n } @else {\n <span class=\"sr-only\">Loading</span>\n }\n `,\n styles: `\n @keyframes com-spin {\n to {\n transform: rotate(360deg);\n }\n }\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n role: 'status',\n '[class]': 'containerClasses()',\n },\n})\nexport class ComSpinner {\n /**\n * Optional loading text (e.g., \"Loading...\", \"Saving...\").\n * When omitted, \"Loading\" is rendered for screen readers only (sr-only).\n * When provided, the label is visible next to or below the spinner.\n */\n readonly label: InputSignal<string | undefined> = input<string>();\n\n /**\n * Position of the label relative to the spinner.\n * Only relevant when a visible `label` is provided.\n */\n readonly labelPosition: InputSignal<SpinnerLabelPosition> = input<SpinnerLabelPosition>('right');\n\n /** Size of the spinner. */\n readonly size: InputSignal<SpinnerSize> = input<SpinnerSize>('md');\n\n /**\n * Color of the spinner.\n * `current` inherits from `currentColor` to match surrounding text.\n */\n readonly color: InputSignal<SpinnerColor> = input<SpinnerColor>('current');\n\n /** @internal Computed classes for the spinning circle. */\n protected readonly spinnerClasses: Signal<string> = computed(() =>\n spinnerVariants({\n size: this.size(),\n color: this.color(),\n })\n );\n\n /** @internal Computed classes for the container/host. */\n protected readonly containerClasses: Signal<string> = computed(() =>\n this.label()\n ? spinnerContainerVariants({ labelPosition: this.labelPosition() })\n : spinnerContainerVariants()\n );\n\n /** @internal Computed label font size class. */\n protected readonly labelSizeClass: Signal<string> = computed(\n () => SPINNER_LABEL_SIZES[this.size()]\n );\n}\n","// Public API for the spinner component\n\n// Main component\nexport { ComSpinner } from './spinner.component';\n\n// Variants (for advanced customization)\nexport {\n spinnerVariants,\n spinnerContainerVariants,\n SPINNER_LABEL_SIZES,\n} from './spinner.variants';\n\nexport type {\n SpinnerSize,\n SpinnerColor,\n SpinnerLabelPosition,\n} from './spinner.variants';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}