pdm-ui-kit 0.1.0

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 (122) hide show
  1. package/FIGMA_COMPONENT_AUDIT.md +154 -0
  2. package/README.md +72 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +29 -0
  5. package/src/lib/components/accordion/accordion.component.html +34 -0
  6. package/src/lib/components/accordion/accordion.component.ts +38 -0
  7. package/src/lib/components/alert/alert.component.html +52 -0
  8. package/src/lib/components/alert/alert.component.ts +25 -0
  9. package/src/lib/components/alert-dialog/alert-dialog.component.html +41 -0
  10. package/src/lib/components/alert-dialog/alert-dialog.component.ts +45 -0
  11. package/src/lib/components/aspect-ratio/aspect-ratio.component.html +11 -0
  12. package/src/lib/components/aspect-ratio/aspect-ratio.component.ts +18 -0
  13. package/src/lib/components/avatar/avatar.component.html +21 -0
  14. package/src/lib/components/avatar/avatar.component.ts +32 -0
  15. package/src/lib/components/badge/badge.component.html +28 -0
  16. package/src/lib/components/badge/badge.component.ts +23 -0
  17. package/src/lib/components/breadcrumb/breadcrumb.component.html +39 -0
  18. package/src/lib/components/breadcrumb/breadcrumb.component.ts +26 -0
  19. package/src/lib/components/button/button.component.html +15 -0
  20. package/src/lib/components/button/button.component.ts +84 -0
  21. package/src/lib/components/button-group/button-group.component.html +39 -0
  22. package/src/lib/components/button-group/button-group.component.ts +15 -0
  23. package/src/lib/components/calendar/calendar.component.html +73 -0
  24. package/src/lib/components/calendar/calendar.component.ts +78 -0
  25. package/src/lib/components/card/card.component.html +77 -0
  26. package/src/lib/components/card/card.component.ts +39 -0
  27. package/src/lib/components/carousel/carousel.component.html +86 -0
  28. package/src/lib/components/carousel/carousel.component.ts +100 -0
  29. package/src/lib/components/chart/chart.component.html +143 -0
  30. package/src/lib/components/chart/chart.component.ts +147 -0
  31. package/src/lib/components/checkbox/checkbox.component.html +38 -0
  32. package/src/lib/components/checkbox/checkbox.component.ts +32 -0
  33. package/src/lib/components/collapsible/collapsible.component.html +26 -0
  34. package/src/lib/components/collapsible/collapsible.component.ts +29 -0
  35. package/src/lib/components/combobox/combobox.component.html +42 -0
  36. package/src/lib/components/combobox/combobox.component.ts +32 -0
  37. package/src/lib/components/command/command.component.html +55 -0
  38. package/src/lib/components/command/command.component.ts +67 -0
  39. package/src/lib/components/context-menu/context-menu.component.html +47 -0
  40. package/src/lib/components/context-menu/context-menu.component.ts +67 -0
  41. package/src/lib/components/data-table/data-table.component.html +63 -0
  42. package/src/lib/components/data-table/data-table.component.ts +78 -0
  43. package/src/lib/components/date-picker/date-picker.component.html +38 -0
  44. package/src/lib/components/date-picker/date-picker.component.ts +34 -0
  45. package/src/lib/components/dialog/dialog.component.html +78 -0
  46. package/src/lib/components/dialog/dialog.component.ts +55 -0
  47. package/src/lib/components/drawer/drawer.component.html +56 -0
  48. package/src/lib/components/drawer/drawer.component.ts +43 -0
  49. package/src/lib/components/dropdown-menu/dropdown-menu.component.html +56 -0
  50. package/src/lib/components/dropdown-menu/dropdown-menu.component.ts +126 -0
  51. package/src/lib/components/empty/empty.component.html +29 -0
  52. package/src/lib/components/empty/empty.component.ts +35 -0
  53. package/src/lib/components/field/field.component.html +22 -0
  54. package/src/lib/components/field/field.component.ts +28 -0
  55. package/src/lib/components/hover-card/hover-card.component.html +24 -0
  56. package/src/lib/components/hover-card/hover-card.component.ts +36 -0
  57. package/src/lib/components/icon/icon.component.html +286 -0
  58. package/src/lib/components/icon/icon.component.ts +133 -0
  59. package/src/lib/components/input/input.component.html +22 -0
  60. package/src/lib/components/input/input.component.ts +33 -0
  61. package/src/lib/components/input-group/input-group.component.html +31 -0
  62. package/src/lib/components/input-group/input-group.component.ts +26 -0
  63. package/src/lib/components/input-otp/input-otp.component.html +25 -0
  64. package/src/lib/components/input-otp/input-otp.component.ts +146 -0
  65. package/src/lib/components/input-password/input-password.component.html +64 -0
  66. package/src/lib/components/input-password/input-password.component.ts +46 -0
  67. package/src/lib/components/item/item.component.html +10 -0
  68. package/src/lib/components/item/item.component.ts +12 -0
  69. package/src/lib/components/kbd/kbd.component.html +3 -0
  70. package/src/lib/components/kbd/kbd.component.ts +10 -0
  71. package/src/lib/components/label/label.component.html +7 -0
  72. package/src/lib/components/label/label.component.ts +12 -0
  73. package/src/lib/components/menubar/menubar.component.html +16 -0
  74. package/src/lib/components/menubar/menubar.component.ts +29 -0
  75. package/src/lib/components/native-select/native-select.component.html +17 -0
  76. package/src/lib/components/native-select/native-select.component.ts +28 -0
  77. package/src/lib/components/navigation-menu/navigation-menu.component.html +15 -0
  78. package/src/lib/components/navigation-menu/navigation-menu.component.ts +17 -0
  79. package/src/lib/components/pagination/pagination.component.html +30 -0
  80. package/src/lib/components/pagination/pagination.component.ts +37 -0
  81. package/src/lib/components/popover/popover.component.html +6 -0
  82. package/src/lib/components/popover/popover.component.ts +40 -0
  83. package/src/lib/components/progress/progress.component.html +9 -0
  84. package/src/lib/components/progress/progress.component.ts +20 -0
  85. package/src/lib/components/radio-group/radio-group.component.html +25 -0
  86. package/src/lib/components/radio-group/radio-group.component.ts +30 -0
  87. package/src/lib/components/scroll-area/scroll-area.component.html +5 -0
  88. package/src/lib/components/scroll-area/scroll-area.component.ts +11 -0
  89. package/src/lib/components/select/select.component.html +14 -0
  90. package/src/lib/components/select/select.component.ts +27 -0
  91. package/src/lib/components/separator/separator.component.html +5 -0
  92. package/src/lib/components/separator/separator.component.ts +16 -0
  93. package/src/lib/components/sheet/sheet.component.html +10 -0
  94. package/src/lib/components/sheet/sheet.component.ts +28 -0
  95. package/src/lib/components/sidebar/sidebar.component.html +3 -0
  96. package/src/lib/components/sidebar/sidebar.component.ts +11 -0
  97. package/src/lib/components/skeleton/skeleton.component.html +1 -0
  98. package/src/lib/components/skeleton/skeleton.component.ts +10 -0
  99. package/src/lib/components/slider/slider.component.html +15 -0
  100. package/src/lib/components/slider/slider.component.ts +31 -0
  101. package/src/lib/components/sonner/sonner.component.html +10 -0
  102. package/src/lib/components/sonner/sonner.component.ts +25 -0
  103. package/src/lib/components/spinner/spinner.component.html +6 -0
  104. package/src/lib/components/spinner/spinner.component.ts +11 -0
  105. package/src/lib/components/switch/switch.component.html +14 -0
  106. package/src/lib/components/switch/switch.component.ts +20 -0
  107. package/src/lib/components/table/table.component.html +5 -0
  108. package/src/lib/components/table/table.component.ts +10 -0
  109. package/src/lib/components/tabs/tabs.component.html +21 -0
  110. package/src/lib/components/tabs/tabs.component.ts +26 -0
  111. package/src/lib/components/textarea/textarea.component.html +21 -0
  112. package/src/lib/components/textarea/textarea.component.ts +28 -0
  113. package/src/lib/components/toggle/toggle.component.html +16 -0
  114. package/src/lib/components/toggle/toggle.component.ts +29 -0
  115. package/src/lib/components/toggle-group/toggle-group.component.html +17 -0
  116. package/src/lib/components/toggle-group/toggle-group.component.ts +26 -0
  117. package/src/lib/components/tooltip/tooltip.component.html +6 -0
  118. package/src/lib/components/tooltip/tooltip.component.ts +20 -0
  119. package/src/lib/pdm-ui-kit.module.ts +126 -0
  120. package/src/public-api.ts +58 -0
  121. package/tsconfig.lib.json +17 -0
  122. package/tsconfig.lib.prod.json +9 -0
@@ -0,0 +1,11 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-scroll-area',
5
+ templateUrl: './scroll-area.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmScrollAreaComponent {
9
+ @Input() maxHeight = '16rem';
10
+ @Input() className = '';
11
+ }
@@ -0,0 +1,14 @@
1
+ <select
2
+ [id]="id"
3
+ [value]="value"
4
+ [disabled]="disabled"
5
+ [attr.aria-invalid]="invalid"
6
+ [ngClass]="[
7
+ 'flex h-9 w-full rounded-[8px] border bg-[hsl(var(--background))] px-3 py-2 text-sm leading-5 text-[hsl(var(--foreground))] shadow-[0_1px_2px_rgba(0,0,0,0.1)] ring-offset-[hsl(var(--background))] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[hsl(var(--foreground))] disabled:cursor-not-allowed disabled:opacity-50',
8
+ invalid ? 'border-[hsl(var(--destructive))]' : 'border-[hsl(var(--input))]',
9
+ className
10
+ ]"
11
+ (change)="onChange($event)"
12
+ >
13
+ <option *ngFor="let option of options" [value]="option.value" [disabled]="option.disabled">{{ option.label }}</option>
14
+ </select>
@@ -0,0 +1,27 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ export interface PdmSelectOption {
4
+ label: string;
5
+ value: string;
6
+ disabled?: boolean;
7
+ }
8
+
9
+ @Component({
10
+ selector: 'pdm-select',
11
+ templateUrl: './select.component.html',
12
+ changeDetection: ChangeDetectionStrategy.OnPush
13
+ })
14
+ export class PdmSelectComponent {
15
+ @Input() id = '';
16
+ @Input() value = '';
17
+ @Input() options: PdmSelectOption[] = [];
18
+ @Input() disabled = false;
19
+ @Input() invalid = false;
20
+ @Input() className = '';
21
+
22
+ @Output() valueChange = new EventEmitter<string>();
23
+
24
+ onChange(event: Event): void {
25
+ this.valueChange.emit((event.target as HTMLSelectElement).value);
26
+ }
27
+ }
@@ -0,0 +1,5 @@
1
+ <div
2
+ [attr.role]="decorative ? 'none' : 'separator'"
3
+ [attr.aria-orientation]="orientation"
4
+ [ngClass]="['shrink-0 bg-[hsl(var(--border))]', orientationClass, className]"
5
+ ></div>
@@ -0,0 +1,16 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-separator',
5
+ templateUrl: './separator.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmSeparatorComponent {
9
+ @Input() orientation: 'horizontal' | 'vertical' = 'horizontal';
10
+ @Input() decorative = true;
11
+ @Input() className = '';
12
+
13
+ get orientationClass(): string {
14
+ return this.orientation === 'vertical' ? 'h-full w-px' : 'h-px w-full';
15
+ }
16
+ }
@@ -0,0 +1,10 @@
1
+ <div *ngIf="open" class="fixed inset-0 z-50">
2
+ <button type="button" class="absolute inset-0 bg-black/55" aria-label="Close sheet" (click)="close()"></button>
3
+
4
+ <section [ngClass]="['absolute bg-[hsl(var(--background))] border-[hsl(var(--border))] p-6 shadow-[0_4px_6px_rgba(0,0,0,0.09)]', panelClass, className]" role="dialog" aria-modal="true">
5
+ <button type="button" class="absolute right-3 top-3 rounded-sm opacity-70 transition-opacity hover:opacity-100" (click)="close()">
6
+ <pdm-icon name="x" [size]="16"></pdm-icon>
7
+ </button>
8
+ <ng-content></ng-content>
9
+ </section>
10
+ </div>
@@ -0,0 +1,28 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ export type PdmSheetSide = 'top' | 'right' | 'bottom' | 'left';
4
+
5
+ @Component({
6
+ selector: 'pdm-sheet',
7
+ templateUrl: './sheet.component.html',
8
+ changeDetection: ChangeDetectionStrategy.OnPush
9
+ })
10
+ export class PdmSheetComponent {
11
+ @Input() open = false;
12
+ @Input() side: PdmSheetSide = 'right';
13
+ @Input() className = '';
14
+
15
+ @Output() openChange = new EventEmitter<boolean>();
16
+
17
+ close(): void {
18
+ this.openChange.emit(false);
19
+ }
20
+
21
+ get panelClass(): string {
22
+ if (this.side === 'left') return 'left-0 top-0 h-full w-full max-w-[360px] border-r';
23
+ if (this.side === 'top') return 'top-0 left-0 w-full border-b';
24
+ if (this.side === 'bottom') return 'bottom-0 left-0 w-full border-t';
25
+
26
+ return 'right-0 top-0 h-full w-full max-w-[360px] border-l';
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ <aside [ngClass]="['h-full border-r border-[hsl(var(--border))] bg-[hsl(var(--background))] transition-all', collapsed ? 'w-14' : 'w-64', className]">
2
+ <ng-content></ng-content>
3
+ </aside>
@@ -0,0 +1,11 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-sidebar',
5
+ templateUrl: './sidebar.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmSidebarComponent {
9
+ @Input() collapsed = false;
10
+ @Input() className = '';
11
+ }
@@ -0,0 +1 @@
1
+ <div [ngClass]="['animate-pulse rounded-md bg-[hsl(var(--muted))]', className]"></div>
@@ -0,0 +1,10 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-skeleton',
5
+ templateUrl: './skeleton.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmSkeletonComponent {
9
+ @Input() className = '';
10
+ }
@@ -0,0 +1,15 @@
1
+ <div [ngClass]="['relative h-5 w-full', className]">
2
+ <div class="absolute left-0 right-0 top-1/2 h-1 -translate-y-1/2 rounded-full bg-[hsl(var(--muted))]"></div>
3
+ <div class="absolute left-0 top-1/2 h-1 -translate-y-1/2 rounded-full bg-[hsl(var(--foreground))]" [style.width.%]="percentage"></div>
4
+ <div class="absolute top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border border-[hsl(var(--foreground))] bg-[hsl(var(--background))] shadow-[0_1px_2px_rgba(0,0,0,0.1)]" [style.left.%]="percentage"></div>
5
+ <input
6
+ type="range"
7
+ [min]="min"
8
+ [max]="max"
9
+ [step]="step"
10
+ [value]="value"
11
+ [disabled]="disabled"
12
+ class="absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed"
13
+ (input)="onInput($event)"
14
+ />
15
+ </div>
@@ -0,0 +1,31 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-slider',
5
+ templateUrl: './slider.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmSliderComponent {
9
+ @Input() min = 0;
10
+ @Input() max = 100;
11
+ @Input() step = 1;
12
+ @Input() value = 0;
13
+ @Input() disabled = false;
14
+ @Input() className = '';
15
+
16
+ @Output() valueChange = new EventEmitter<number>();
17
+
18
+ get percentage(): number {
19
+ const safeRange = this.max - this.min;
20
+ if (safeRange <= 0) {
21
+ return 0;
22
+ }
23
+
24
+ const bounded = Math.min(this.max, Math.max(this.min, this.value));
25
+ return ((bounded - this.min) / safeRange) * 100;
26
+ }
27
+
28
+ onInput(event: Event): void {
29
+ this.valueChange.emit(Number((event.target as HTMLInputElement).value));
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ <div [ngClass]="['flex w-full max-w-sm items-start gap-3 rounded-lg border p-4 shadow-lg', toneClass, className]" role="status" aria-live="polite">
2
+ <div class="grid gap-0.5">
3
+ <p *ngIf="title" class="text-sm font-semibold">{{ title }}</p>
4
+ <p *ngIf="description" class="text-sm opacity-90">{{ description }}</p>
5
+ <ng-content></ng-content>
6
+ </div>
7
+ <button type="button" class="ml-auto rounded-sm opacity-70 transition-opacity hover:opacity-100" (click)="dismissed.emit()" aria-label="Dismiss">
8
+ <pdm-icon name="x" [size]="14"></pdm-icon>
9
+ </button>
10
+ </div>
@@ -0,0 +1,25 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ export type PdmToastTone = 'default' | 'success' | 'error' | 'warning';
4
+
5
+ @Component({
6
+ selector: 'pdm-sonner',
7
+ templateUrl: './sonner.component.html',
8
+ changeDetection: ChangeDetectionStrategy.OnPush
9
+ })
10
+ export class PdmSonnerComponent {
11
+ @Input() title = '';
12
+ @Input() description = '';
13
+ @Input() tone: PdmToastTone = 'default';
14
+ @Input() className = '';
15
+
16
+ @Output() dismissed = new EventEmitter<void>();
17
+
18
+ get toneClass(): string {
19
+ if (this.tone === 'success') return 'border-emerald-200 bg-emerald-50 text-emerald-900';
20
+ if (this.tone === 'error') return 'border-red-200 bg-red-50 text-red-900';
21
+ if (this.tone === 'warning') return 'border-amber-200 bg-amber-50 text-amber-900';
22
+
23
+ return 'border-[hsl(var(--border))] bg-[hsl(var(--background))] text-[hsl(var(--foreground))]';
24
+ }
25
+ }
@@ -0,0 +1,6 @@
1
+ <span
2
+ [style.width.px]="size"
3
+ [style.height.px]="size"
4
+ [ngClass]="['inline-block animate-spin rounded-full border-2 border-current border-r-transparent', className]"
5
+ aria-hidden="true"
6
+ ></span>
@@ -0,0 +1,11 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-spinner',
5
+ templateUrl: './spinner.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmSpinnerComponent {
9
+ @Input() size = 16;
10
+ @Input() className = '';
11
+ }
@@ -0,0 +1,14 @@
1
+ <label [attr.for]="id" [ngClass]="['inline-flex items-center gap-2', className]">
2
+ <input
3
+ [id]="id"
4
+ type="checkbox"
5
+ class="peer sr-only"
6
+ [checked]="checked"
7
+ [disabled]="disabled"
8
+ (change)="onToggle($event)"
9
+ />
10
+ <span class="relative inline-flex h-6 w-11 items-center rounded-full border border-transparent bg-[hsl(var(--muted))] transition-colors peer-focus-visible:ring-1 peer-focus-visible:ring-[hsl(var(--foreground))] peer-disabled:opacity-50 peer-checked:bg-[hsl(var(--foreground))]">
11
+ <span class="h-5 w-5 translate-x-0.5 rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.1)] transition-transform peer-checked:translate-x-5"></span>
12
+ </span>
13
+ <span *ngIf="label" class="text-sm leading-5 text-[hsl(var(--foreground))]">{{ label }}</span>
14
+ </label>
@@ -0,0 +1,20 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-switch',
5
+ templateUrl: './switch.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmSwitchComponent {
9
+ @Input() id = '';
10
+ @Input() checked = false;
11
+ @Input() disabled = false;
12
+ @Input() label = '';
13
+ @Input() className = '';
14
+
15
+ @Output() checkedChange = new EventEmitter<boolean>();
16
+
17
+ onToggle(event: Event): void {
18
+ this.checkedChange.emit((event.target as HTMLInputElement).checked);
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ <div [ngClass]="['relative w-full overflow-auto', className]">
2
+ <table class="w-full caption-bottom text-sm">
3
+ <ng-content></ng-content>
4
+ </table>
5
+ </div>
@@ -0,0 +1,10 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-table',
5
+ templateUrl: './table.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmTableComponent {
9
+ @Input() className = '';
10
+ }
@@ -0,0 +1,21 @@
1
+ <div [ngClass]="['w-full', className]">
2
+ <div role="tablist" class="inline-flex items-start rounded-[6px] bg-[hsl(var(--muted))] p-[5px]">
3
+ <button
4
+ *ngFor="let item of items"
5
+ role="tab"
6
+ [attr.aria-selected]="value === item.value"
7
+ [disabled]="item.disabled"
8
+ [ngClass]="[
9
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-[3px] px-3 py-1.5 text-sm font-medium leading-5 ring-offset-[hsl(var(--background))] transition-all focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[hsl(var(--foreground))] disabled:pointer-events-none disabled:opacity-50',
10
+ value === item.value ? 'bg-[hsl(var(--background))] text-[hsl(var(--foreground))] shadow-[0_1px_2px_rgba(0,0,0,0.1)]' : 'text-[hsl(var(--muted-foreground))]'
11
+ ]"
12
+ (click)="select(item)"
13
+ type="button"
14
+ >
15
+ {{ item.label }}
16
+ </button>
17
+ </div>
18
+ <div class="mt-4">
19
+ <ng-content></ng-content>
20
+ </div>
21
+ </div>
@@ -0,0 +1,26 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ export interface PdmTabItem {
4
+ label: string;
5
+ value: string;
6
+ disabled?: boolean;
7
+ }
8
+
9
+ @Component({
10
+ selector: 'pdm-tabs',
11
+ templateUrl: './tabs.component.html',
12
+ changeDetection: ChangeDetectionStrategy.OnPush
13
+ })
14
+ export class PdmTabsComponent {
15
+ @Input() items: PdmTabItem[] = [];
16
+ @Input() value = '';
17
+ @Input() className = '';
18
+
19
+ @Output() valueChange = new EventEmitter<string>();
20
+
21
+ select(item: PdmTabItem): void {
22
+ if (!item.disabled) {
23
+ this.valueChange.emit(item.value);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,21 @@
1
+ <div [ngClass]="['grid w-full gap-2', className]">
2
+ <label *ngIf="label" [attr.for]="id" class="text-sm font-medium leading-5 text-[hsl(var(--foreground))]">{{ label }}</label>
3
+ <textarea
4
+ [id]="id"
5
+ [rows]="rows"
6
+ [value]="value"
7
+ [placeholder]="placeholder"
8
+ [disabled]="disabled"
9
+ [readonly]="readonly"
10
+ [required]="required"
11
+ [attr.aria-invalid]="invalid"
12
+ [ngClass]="[
13
+ 'flex min-h-[65px] w-full rounded-[8px] border bg-[hsl(var(--background))] px-3 py-2 text-sm leading-5 shadow-[0_1px_2px_rgba(0,0,0,0.1)] ring-offset-[hsl(var(--background))] placeholder:text-[hsl(var(--muted-foreground))] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[hsl(var(--foreground))] disabled:cursor-not-allowed disabled:opacity-50',
14
+ invalid ? 'border-[hsl(var(--destructive))]' : 'border-[hsl(var(--input))]',
15
+ textareaClassName
16
+ ]"
17
+ (input)="onInput($event)"
18
+ ></textarea>
19
+ <p *ngIf="!invalid && helperText" class="text-sm leading-5 text-[hsl(var(--muted-foreground))]">{{ helperText }}</p>
20
+ <p *ngIf="invalid && errorText" class="text-sm leading-5 text-[hsl(var(--destructive))]">{{ errorText }}</p>
21
+ </div>
@@ -0,0 +1,28 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-textarea',
5
+ templateUrl: './textarea.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmTextareaComponent {
9
+ @Input() id = '';
10
+ @Input() value = '';
11
+ @Input() placeholder = '';
12
+ @Input() rows = 4;
13
+ @Input() disabled = false;
14
+ @Input() readonly = false;
15
+ @Input() required = false;
16
+ @Input() invalid = false;
17
+ @Input() className = '';
18
+ @Input() textareaClassName = '';
19
+ @Input() label = '';
20
+ @Input() helperText = '';
21
+ @Input() errorText = '';
22
+
23
+ @Output() valueChange = new EventEmitter<string>();
24
+
25
+ onInput(event: Event): void {
26
+ this.valueChange.emit((event.target as HTMLTextAreaElement).value);
27
+ }
28
+ }
@@ -0,0 +1,16 @@
1
+ <button
2
+ type="button"
3
+ [disabled]="disabled"
4
+ [attr.aria-pressed]="pressed"
5
+ [ngClass]="[
6
+ 'inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50',
7
+ sizeClass,
8
+ pressed
9
+ ? 'bg-[hsl(var(--accent))] text-[hsl(var(--accent-foreground))]'
10
+ : 'bg-transparent text-[hsl(var(--foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]',
11
+ className
12
+ ]"
13
+ (click)="toggle()"
14
+ >
15
+ <ng-content></ng-content>
16
+ </button>
@@ -0,0 +1,29 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ export type PdmToggleSize = 'sm' | 'default' | 'lg';
4
+
5
+ @Component({
6
+ selector: 'pdm-toggle',
7
+ templateUrl: './toggle.component.html',
8
+ changeDetection: ChangeDetectionStrategy.OnPush
9
+ })
10
+ export class PdmToggleComponent {
11
+ @Input() pressed = false;
12
+ @Input() disabled = false;
13
+ @Input() size: PdmToggleSize = 'default';
14
+ @Input() className = '';
15
+
16
+ @Output() pressedChange = new EventEmitter<boolean>();
17
+
18
+ toggle(): void {
19
+ if (!this.disabled) {
20
+ this.pressedChange.emit(!this.pressed);
21
+ }
22
+ }
23
+
24
+ get sizeClass(): string {
25
+ if (this.size === 'sm') return 'h-8 px-2.5 min-w-[32px]';
26
+ if (this.size === 'lg') return 'h-10 px-4 min-w-[40px]';
27
+ return 'h-9 px-3 min-w-[36px]';
28
+ }
29
+ }
@@ -0,0 +1,17 @@
1
+ <div [ngClass]="['inline-flex items-center rounded-md border border-[hsl(var(--border))] p-1', className]" role="group">
2
+ <button
3
+ *ngFor="let item of items"
4
+ type="button"
5
+ [disabled]="item.disabled"
6
+ [attr.aria-pressed]="item.value === value"
7
+ [ngClass]="[
8
+ 'inline-flex h-8 items-center justify-center rounded-sm px-2.5 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50',
9
+ item.value === value
10
+ ? 'bg-[hsl(var(--accent))] text-[hsl(var(--accent-foreground))]'
11
+ : 'text-[hsl(var(--foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]'
12
+ ]"
13
+ (click)="onSelect(item.value, item.disabled)"
14
+ >
15
+ {{ item.label }}
16
+ </button>
17
+ </div>
@@ -0,0 +1,26 @@
1
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2
+
3
+ export interface PdmToggleGroupItem {
4
+ label: string;
5
+ value: string;
6
+ disabled?: boolean;
7
+ }
8
+
9
+ @Component({
10
+ selector: 'pdm-toggle-group',
11
+ templateUrl: './toggle-group.component.html',
12
+ changeDetection: ChangeDetectionStrategy.OnPush
13
+ })
14
+ export class PdmToggleGroupComponent {
15
+ @Input() items: PdmToggleGroupItem[] = [];
16
+ @Input() value = '';
17
+ @Input() className = '';
18
+
19
+ @Output() valueChange = new EventEmitter<string>();
20
+
21
+ onSelect(next: string, disabled?: boolean): void {
22
+ if (!disabled && next !== this.value) {
23
+ this.valueChange.emit(next);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ <span class="relative inline-flex" [ngClass]="className" (mouseenter)="open = true" (mouseleave)="open = false" (focusin)="open = true" (focusout)="open = false">
2
+ <ng-content></ng-content>
3
+ <span *ngIf="open" [ngClass]="['pointer-events-none absolute z-50 overflow-hidden rounded-md bg-[hsl(var(--foreground))] px-3 py-1.5 text-xs text-[hsl(var(--background))] animate-in fade-in-0 zoom-in-95', positionClass]">
4
+ {{ text }}
5
+ </span>
6
+ </span>
@@ -0,0 +1,20 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'pdm-tooltip',
5
+ templateUrl: './tooltip.component.html',
6
+ changeDetection: ChangeDetectionStrategy.OnPush
7
+ })
8
+ export class PdmTooltipComponent {
9
+ @Input() text = '';
10
+ @Input() side: 'top' | 'right' | 'bottom' | 'left' = 'top';
11
+ @Input() className = '';
12
+ open = false;
13
+
14
+ get positionClass(): string {
15
+ if (this.side === 'bottom') return 'top-full left-1/2 -translate-x-1/2 mt-2';
16
+ if (this.side === 'left') return 'right-full top-1/2 -translate-y-1/2 mr-2';
17
+ if (this.side === 'right') return 'left-full top-1/2 -translate-y-1/2 ml-2';
18
+ return 'bottom-full left-1/2 -translate-x-1/2 mb-2';
19
+ }
20
+ }