@sonny-ui/core 0.1.0-alpha.7 → 0.1.0-alpha.9

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 (94) hide show
  1. package/package.json +1 -1
  2. package/schematics/ng-generate/component/index.js +1 -1
  3. package/src/lib/accordion/accordion.directives.spec.ts +95 -0
  4. package/src/lib/accordion/accordion.directives.ts +104 -0
  5. package/src/lib/accordion/index.ts +8 -0
  6. package/src/lib/avatar/avatar.component.spec.ts +75 -0
  7. package/src/lib/avatar/avatar.component.ts +43 -0
  8. package/src/lib/avatar/avatar.variants.ts +26 -0
  9. package/src/lib/avatar/index.ts +2 -0
  10. package/src/lib/badge/badge.directive.spec.ts +74 -0
  11. package/src/lib/badge/badge.directive.ts +18 -0
  12. package/src/lib/badge/badge.variants.ts +29 -0
  13. package/src/lib/badge/index.ts +2 -0
  14. package/src/lib/breadcrumb/breadcrumb.directives.spec.ts +80 -0
  15. package/src/lib/breadcrumb/breadcrumb.directives.ts +84 -0
  16. package/src/lib/breadcrumb/index.ts +8 -0
  17. package/src/lib/button/button.directive.spec.ts +92 -0
  18. package/src/lib/button/button.directive.ts +29 -0
  19. package/src/lib/button/button.variants.ts +30 -0
  20. package/src/lib/button/index.ts +2 -0
  21. package/src/lib/button-group/button-group.directive.spec.ts +46 -0
  22. package/src/lib/button-group/button-group.directive.ts +20 -0
  23. package/src/lib/button-group/button-group.variants.ts +18 -0
  24. package/src/lib/button-group/index.ts +2 -0
  25. package/src/lib/card/card.directives.spec.ts +104 -0
  26. package/src/lib/card/card.directives.ts +78 -0
  27. package/src/lib/card/card.variants.ts +28 -0
  28. package/src/lib/card/index.ts +9 -0
  29. package/src/lib/checkbox/checkbox.directive.spec.ts +57 -0
  30. package/src/lib/checkbox/checkbox.directive.ts +17 -0
  31. package/src/lib/checkbox/checkbox.variants.ts +19 -0
  32. package/src/lib/checkbox/index.ts +2 -0
  33. package/src/lib/combobox/combobox.component.spec.ts +93 -0
  34. package/src/lib/combobox/combobox.component.ts +236 -0
  35. package/src/lib/combobox/combobox.variants.ts +19 -0
  36. package/src/lib/combobox/index.ts +2 -0
  37. package/src/lib/input/index.ts +3 -0
  38. package/src/lib/input/input.directive.spec.ts +103 -0
  39. package/src/lib/input/input.directive.ts +26 -0
  40. package/src/lib/input/input.variants.ts +42 -0
  41. package/src/lib/input/label.directive.ts +17 -0
  42. package/src/lib/loader/index.ts +2 -0
  43. package/src/lib/loader/loader.component.spec.ts +58 -0
  44. package/src/lib/loader/loader.component.ts +47 -0
  45. package/src/lib/loader/loader.variants.ts +21 -0
  46. package/src/lib/modal/dialog-ref.ts +19 -0
  47. package/src/lib/modal/dialog.directives.ts +90 -0
  48. package/src/lib/modal/dialog.service.spec.ts +52 -0
  49. package/src/lib/modal/dialog.service.ts +61 -0
  50. package/src/lib/modal/dialog.types.ts +16 -0
  51. package/src/lib/modal/index.ts +11 -0
  52. package/src/lib/radio/index.ts +2 -0
  53. package/src/lib/radio/radio.directive.spec.ts +46 -0
  54. package/src/lib/radio/radio.directive.ts +17 -0
  55. package/src/lib/radio/radio.variants.ts +19 -0
  56. package/src/lib/select/index.ts +2 -0
  57. package/src/lib/select/select.component.spec.ts +56 -0
  58. package/src/lib/select/select.component.ts +206 -0
  59. package/src/lib/select/select.variants.ts +19 -0
  60. package/src/lib/sheet/index.ts +10 -0
  61. package/src/lib/sheet/sheet-ref.ts +18 -0
  62. package/src/lib/sheet/sheet.component.spec.ts +67 -0
  63. package/src/lib/sheet/sheet.directives.ts +75 -0
  64. package/src/lib/sheet/sheet.service.ts +100 -0
  65. package/src/lib/sheet/sheet.types.ts +23 -0
  66. package/src/lib/skeleton/index.ts +2 -0
  67. package/src/lib/skeleton/skeleton.directive.spec.ts +55 -0
  68. package/src/lib/skeleton/skeleton.directive.ts +18 -0
  69. package/src/lib/skeleton/skeleton.variants.ts +27 -0
  70. package/src/lib/slider/index.ts +2 -0
  71. package/src/lib/slider/slider.component.spec.ts +55 -0
  72. package/src/lib/slider/slider.component.ts +141 -0
  73. package/src/lib/slider/slider.variants.ts +25 -0
  74. package/src/lib/switch/index.ts +2 -0
  75. package/src/lib/switch/switch.component.spec.ts +50 -0
  76. package/src/lib/switch/switch.component.ts +43 -0
  77. package/src/lib/switch/switch.variants.ts +31 -0
  78. package/src/lib/table/index.ts +12 -0
  79. package/src/lib/table/table.directives.spec.ts +111 -0
  80. package/src/lib/table/table.directives.ts +134 -0
  81. package/src/lib/table/table.variants.ts +36 -0
  82. package/src/lib/tabs/index.ts +8 -0
  83. package/src/lib/tabs/tabs.directives.spec.ts +66 -0
  84. package/src/lib/tabs/tabs.directives.ts +91 -0
  85. package/src/lib/tabs/tabs.variants.ts +17 -0
  86. package/src/lib/toast/index.ts +3 -0
  87. package/src/lib/toast/toast.service.spec.ts +71 -0
  88. package/src/lib/toast/toast.service.ts +60 -0
  89. package/src/lib/toast/toast.variants.ts +38 -0
  90. package/src/lib/toast/toaster.component.ts +80 -0
  91. package/src/lib/toggle/index.ts +2 -0
  92. package/src/lib/toggle/toggle.directive.spec.ts +52 -0
  93. package/src/lib/toggle/toggle.directive.ts +27 -0
  94. package/src/lib/toggle/toggle.variants.ts +25 -0
@@ -0,0 +1,38 @@
1
+ import { cva } from 'class-variance-authority';
2
+
3
+ export const toastVariants = cva(
4
+ 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-sm border p-6 pr-8 shadow-lg transition-all',
5
+ {
6
+ variants: {
7
+ variant: {
8
+ default: 'bg-background border-border text-foreground',
9
+ destructive: 'bg-destructive border-destructive text-destructive-foreground',
10
+ success: 'bg-green-600 border-green-600 text-white',
11
+ warning: 'bg-yellow-500 border-yellow-500 text-white',
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ variant: 'default',
16
+ },
17
+ }
18
+ );
19
+
20
+ export type ToastVariant = 'default' | 'destructive' | 'success' | 'warning';
21
+ export type ToastPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
22
+
23
+ export interface ToastAction {
24
+ label: string;
25
+ onClick: () => void;
26
+ }
27
+
28
+ export interface ToastConfig {
29
+ title: string;
30
+ description?: string;
31
+ variant?: ToastVariant;
32
+ duration?: number;
33
+ action?: ToastAction;
34
+ }
35
+
36
+ export interface ToastData extends ToastConfig {
37
+ id: string;
38
+ }
@@ -0,0 +1,80 @@
1
+ import { Component, inject, input, computed } from '@angular/core';
2
+ import { SnyToastService } from './toast.service';
3
+ import { toastVariants, type ToastPosition, type ToastVariant } from './toast.variants';
4
+ import { cn } from '../core/utils/cn';
5
+
6
+ @Component({
7
+ selector: 'sny-toaster',
8
+ standalone: true,
9
+ template: `
10
+ <div [class]="containerClass()" role="region" aria-label="Notifications" tabindex="-1">
11
+ @for (toast of visibleToasts(); track toast.id) {
12
+ <div
13
+ [class]="toastClasses[toast.variant ?? 'default']"
14
+ role="alert"
15
+ aria-live="polite"
16
+ >
17
+ <div class="grid gap-1">
18
+ <div class="text-sm font-semibold">{{ toast.title }}</div>
19
+ @if (toast.description) {
20
+ <div class="text-sm opacity-90">{{ toast.description }}</div>
21
+ }
22
+ </div>
23
+ <div class="flex items-center gap-2">
24
+ @if (toast.action) {
25
+ <button
26
+ class="inline-flex h-8 shrink-0 items-center justify-center rounded-sm border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary"
27
+ (click)="toast.action!.onClick()"
28
+ >
29
+ {{ toast.action!.label }}
30
+ </button>
31
+ }
32
+ <button
33
+ class="absolute right-2 top-2 rounded-sm p-1 opacity-0 transition-opacity hover:opacity-100 group-hover:opacity-100 focus:opacity-100"
34
+ aria-label="Close"
35
+ (click)="dismiss(toast.id)"
36
+ >
37
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
38
+ </button>
39
+ </div>
40
+ </div>
41
+ }
42
+ </div>
43
+ `,
44
+ })
45
+ export class SnyToasterComponent {
46
+ private readonly toastService = inject(SnyToastService);
47
+
48
+ readonly position = input<ToastPosition>('bottom-right');
49
+ readonly maxToasts = input(5);
50
+
51
+ readonly visibleToasts = computed(() =>
52
+ this.toastService.toasts().slice(-this.maxToasts())
53
+ );
54
+
55
+ readonly containerClass = computed(() => {
56
+ const pos = this.position();
57
+ const base = 'fixed z-[100] flex max-h-screen w-full flex-col-reverse gap-2 p-4 sm:max-w-[420px]';
58
+ const posMap: Record<ToastPosition, string> = {
59
+ 'top-right': 'top-0 right-0',
60
+ 'top-left': 'top-0 left-0',
61
+ 'bottom-right': 'bottom-0 right-0',
62
+ 'bottom-left': 'bottom-0 left-0',
63
+ 'top-center': 'top-0 left-1/2 -translate-x-1/2',
64
+ 'bottom-center': 'bottom-0 left-1/2 -translate-x-1/2',
65
+ };
66
+ return cn(base, posMap[pos]);
67
+ });
68
+
69
+ /** Pre-computed toast classes by variant — avoids method calls in the template. */
70
+ readonly toastClasses: Record<ToastVariant, string> = {
71
+ default: cn(toastVariants({ variant: 'default' })),
72
+ destructive: cn(toastVariants({ variant: 'destructive' })),
73
+ success: cn(toastVariants({ variant: 'success' })),
74
+ warning: cn(toastVariants({ variant: 'warning' })),
75
+ };
76
+
77
+ dismiss(id: string): void {
78
+ this.toastService.dismiss(id);
79
+ }
80
+ }
@@ -0,0 +1,2 @@
1
+ export { SnyToggleDirective } from './toggle.directive';
2
+ export { toggleVariants, type ToggleVariant, type ToggleSize } from './toggle.variants';
@@ -0,0 +1,52 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { TestBed, ComponentFixture } from '@angular/core/testing';
3
+ import { SnyToggleDirective } from './toggle.directive';
4
+ import type { ToggleVariant, ToggleSize } from './toggle.variants';
5
+
6
+ @Component({
7
+ standalone: true,
8
+ imports: [SnyToggleDirective],
9
+ template: `<button snyToggle [variant]="variant()" [size]="size()" [(pressed)]="pressed">Toggle</button>`,
10
+ })
11
+ class TestHostComponent {
12
+ variant = signal<ToggleVariant>('default');
13
+ size = signal<ToggleSize>('md');
14
+ pressed = signal(false);
15
+ }
16
+
17
+ describe('SnyToggleDirective', () => {
18
+ let fixture: ComponentFixture<TestHostComponent>;
19
+ let button: HTMLButtonElement;
20
+
21
+ beforeEach(async () => {
22
+ await TestBed.configureTestingModule({
23
+ imports: [TestHostComponent],
24
+ }).compileComponents();
25
+
26
+ fixture = TestBed.createComponent(TestHostComponent);
27
+ fixture.detectChanges();
28
+ button = fixture.nativeElement.querySelector('button');
29
+ });
30
+
31
+ it('should apply default classes', () => {
32
+ expect(button.className).toContain('inline-flex');
33
+ expect(button.className).toContain('rounded-sm');
34
+ });
35
+
36
+ it('should be unpressed by default', () => {
37
+ expect(button.getAttribute('aria-pressed')).toBe('false');
38
+ });
39
+
40
+ it('should toggle pressed on click', () => {
41
+ button.click();
42
+ fixture.detectChanges();
43
+ expect(button.getAttribute('aria-pressed')).toBe('true');
44
+ expect(button.className).toContain('bg-accent');
45
+ });
46
+
47
+ it('should apply outline variant', () => {
48
+ fixture.componentInstance.variant.set('outline');
49
+ fixture.detectChanges();
50
+ expect(button.className).toContain('border');
51
+ });
52
+ });
@@ -0,0 +1,27 @@
1
+ import { Directive, computed, input, model } from '@angular/core';
2
+ import { cn } from '../core/utils/cn';
3
+ import { toggleVariants, type ToggleVariant, type ToggleSize } from './toggle.variants';
4
+
5
+ @Directive({
6
+ selector: 'button[snyToggle]',
7
+ standalone: true,
8
+ host: {
9
+ '[class]': 'computedClass()',
10
+ '[attr.aria-pressed]': 'pressed()',
11
+ '(click)': 'pressed.set(!pressed())',
12
+ },
13
+ })
14
+ export class SnyToggleDirective {
15
+ readonly variant = input<ToggleVariant>('default');
16
+ readonly size = input<ToggleSize>('md');
17
+ readonly pressed = model(false);
18
+ readonly class = input<string>('');
19
+
20
+ protected readonly computedClass = computed(() =>
21
+ cn(
22
+ toggleVariants({ variant: this.variant(), size: this.size() }),
23
+ this.pressed() ? 'bg-accent text-accent-foreground' : '',
24
+ this.class()
25
+ )
26
+ );
27
+ }
@@ -0,0 +1,25 @@
1
+ import { cva } from 'class-variance-authority';
2
+
3
+ export const toggleVariants = cva(
4
+ 'inline-flex items-center justify-center rounded-sm text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
5
+ {
6
+ variants: {
7
+ variant: {
8
+ default: 'bg-transparent hover:bg-muted hover:text-muted-foreground',
9
+ outline: 'border border-border bg-transparent hover:bg-accent hover:text-accent-foreground',
10
+ },
11
+ size: {
12
+ sm: 'h-9 px-2.5',
13
+ md: 'h-10 px-3',
14
+ lg: 'h-11 px-5',
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ variant: 'default',
19
+ size: 'md',
20
+ },
21
+ }
22
+ );
23
+
24
+ export type ToggleVariant = 'default' | 'outline';
25
+ export type ToggleSize = 'sm' | 'md' | 'lg';