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.
- package/FIGMA_COMPONENT_AUDIT.md +154 -0
- package/README.md +72 -0
- package/ng-package.json +7 -0
- package/package.json +29 -0
- package/src/lib/components/accordion/accordion.component.html +34 -0
- package/src/lib/components/accordion/accordion.component.ts +38 -0
- package/src/lib/components/alert/alert.component.html +52 -0
- package/src/lib/components/alert/alert.component.ts +25 -0
- package/src/lib/components/alert-dialog/alert-dialog.component.html +41 -0
- package/src/lib/components/alert-dialog/alert-dialog.component.ts +45 -0
- package/src/lib/components/aspect-ratio/aspect-ratio.component.html +11 -0
- package/src/lib/components/aspect-ratio/aspect-ratio.component.ts +18 -0
- package/src/lib/components/avatar/avatar.component.html +21 -0
- package/src/lib/components/avatar/avatar.component.ts +32 -0
- package/src/lib/components/badge/badge.component.html +28 -0
- package/src/lib/components/badge/badge.component.ts +23 -0
- package/src/lib/components/breadcrumb/breadcrumb.component.html +39 -0
- package/src/lib/components/breadcrumb/breadcrumb.component.ts +26 -0
- package/src/lib/components/button/button.component.html +15 -0
- package/src/lib/components/button/button.component.ts +84 -0
- package/src/lib/components/button-group/button-group.component.html +39 -0
- package/src/lib/components/button-group/button-group.component.ts +15 -0
- package/src/lib/components/calendar/calendar.component.html +73 -0
- package/src/lib/components/calendar/calendar.component.ts +78 -0
- package/src/lib/components/card/card.component.html +77 -0
- package/src/lib/components/card/card.component.ts +39 -0
- package/src/lib/components/carousel/carousel.component.html +86 -0
- package/src/lib/components/carousel/carousel.component.ts +100 -0
- package/src/lib/components/chart/chart.component.html +143 -0
- package/src/lib/components/chart/chart.component.ts +147 -0
- package/src/lib/components/checkbox/checkbox.component.html +38 -0
- package/src/lib/components/checkbox/checkbox.component.ts +32 -0
- package/src/lib/components/collapsible/collapsible.component.html +26 -0
- package/src/lib/components/collapsible/collapsible.component.ts +29 -0
- package/src/lib/components/combobox/combobox.component.html +42 -0
- package/src/lib/components/combobox/combobox.component.ts +32 -0
- package/src/lib/components/command/command.component.html +55 -0
- package/src/lib/components/command/command.component.ts +67 -0
- package/src/lib/components/context-menu/context-menu.component.html +47 -0
- package/src/lib/components/context-menu/context-menu.component.ts +67 -0
- package/src/lib/components/data-table/data-table.component.html +63 -0
- package/src/lib/components/data-table/data-table.component.ts +78 -0
- package/src/lib/components/date-picker/date-picker.component.html +38 -0
- package/src/lib/components/date-picker/date-picker.component.ts +34 -0
- package/src/lib/components/dialog/dialog.component.html +78 -0
- package/src/lib/components/dialog/dialog.component.ts +55 -0
- package/src/lib/components/drawer/drawer.component.html +56 -0
- package/src/lib/components/drawer/drawer.component.ts +43 -0
- package/src/lib/components/dropdown-menu/dropdown-menu.component.html +56 -0
- package/src/lib/components/dropdown-menu/dropdown-menu.component.ts +126 -0
- package/src/lib/components/empty/empty.component.html +29 -0
- package/src/lib/components/empty/empty.component.ts +35 -0
- package/src/lib/components/field/field.component.html +22 -0
- package/src/lib/components/field/field.component.ts +28 -0
- package/src/lib/components/hover-card/hover-card.component.html +24 -0
- package/src/lib/components/hover-card/hover-card.component.ts +36 -0
- package/src/lib/components/icon/icon.component.html +286 -0
- package/src/lib/components/icon/icon.component.ts +133 -0
- package/src/lib/components/input/input.component.html +22 -0
- package/src/lib/components/input/input.component.ts +33 -0
- package/src/lib/components/input-group/input-group.component.html +31 -0
- package/src/lib/components/input-group/input-group.component.ts +26 -0
- package/src/lib/components/input-otp/input-otp.component.html +25 -0
- package/src/lib/components/input-otp/input-otp.component.ts +146 -0
- package/src/lib/components/input-password/input-password.component.html +64 -0
- package/src/lib/components/input-password/input-password.component.ts +46 -0
- package/src/lib/components/item/item.component.html +10 -0
- package/src/lib/components/item/item.component.ts +12 -0
- package/src/lib/components/kbd/kbd.component.html +3 -0
- package/src/lib/components/kbd/kbd.component.ts +10 -0
- package/src/lib/components/label/label.component.html +7 -0
- package/src/lib/components/label/label.component.ts +12 -0
- package/src/lib/components/menubar/menubar.component.html +16 -0
- package/src/lib/components/menubar/menubar.component.ts +29 -0
- package/src/lib/components/native-select/native-select.component.html +17 -0
- package/src/lib/components/native-select/native-select.component.ts +28 -0
- package/src/lib/components/navigation-menu/navigation-menu.component.html +15 -0
- package/src/lib/components/navigation-menu/navigation-menu.component.ts +17 -0
- package/src/lib/components/pagination/pagination.component.html +30 -0
- package/src/lib/components/pagination/pagination.component.ts +37 -0
- package/src/lib/components/popover/popover.component.html +6 -0
- package/src/lib/components/popover/popover.component.ts +40 -0
- package/src/lib/components/progress/progress.component.html +9 -0
- package/src/lib/components/progress/progress.component.ts +20 -0
- package/src/lib/components/radio-group/radio-group.component.html +25 -0
- package/src/lib/components/radio-group/radio-group.component.ts +30 -0
- package/src/lib/components/scroll-area/scroll-area.component.html +5 -0
- package/src/lib/components/scroll-area/scroll-area.component.ts +11 -0
- package/src/lib/components/select/select.component.html +14 -0
- package/src/lib/components/select/select.component.ts +27 -0
- package/src/lib/components/separator/separator.component.html +5 -0
- package/src/lib/components/separator/separator.component.ts +16 -0
- package/src/lib/components/sheet/sheet.component.html +10 -0
- package/src/lib/components/sheet/sheet.component.ts +28 -0
- package/src/lib/components/sidebar/sidebar.component.html +3 -0
- package/src/lib/components/sidebar/sidebar.component.ts +11 -0
- package/src/lib/components/skeleton/skeleton.component.html +1 -0
- package/src/lib/components/skeleton/skeleton.component.ts +10 -0
- package/src/lib/components/slider/slider.component.html +15 -0
- package/src/lib/components/slider/slider.component.ts +31 -0
- package/src/lib/components/sonner/sonner.component.html +10 -0
- package/src/lib/components/sonner/sonner.component.ts +25 -0
- package/src/lib/components/spinner/spinner.component.html +6 -0
- package/src/lib/components/spinner/spinner.component.ts +11 -0
- package/src/lib/components/switch/switch.component.html +14 -0
- package/src/lib/components/switch/switch.component.ts +20 -0
- package/src/lib/components/table/table.component.html +5 -0
- package/src/lib/components/table/table.component.ts +10 -0
- package/src/lib/components/tabs/tabs.component.html +21 -0
- package/src/lib/components/tabs/tabs.component.ts +26 -0
- package/src/lib/components/textarea/textarea.component.html +21 -0
- package/src/lib/components/textarea/textarea.component.ts +28 -0
- package/src/lib/components/toggle/toggle.component.html +16 -0
- package/src/lib/components/toggle/toggle.component.ts +29 -0
- package/src/lib/components/toggle-group/toggle-group.component.html +17 -0
- package/src/lib/components/toggle-group/toggle-group.component.ts +26 -0
- package/src/lib/components/tooltip/tooltip.component.html +6 -0
- package/src/lib/components/tooltip/tooltip.component.ts +20 -0
- package/src/lib/pdm-ui-kit.module.ts +126 -0
- package/src/public-api.ts +58 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +9 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<nav
|
|
2
|
+
aria-label="breadcrumb"
|
|
3
|
+
[ngClass]="['inline-flex items-center gap-[6px] text-[14px] leading-5', className]"
|
|
4
|
+
>
|
|
5
|
+
<ng-container *ngFor="let item of renderedItems; let i = index; let last = last">
|
|
6
|
+
<span [ngClass]="[last ? 'text-[#0a0a0a]' : 'text-[#737373]']">{{ item }}</span>
|
|
7
|
+
|
|
8
|
+
<ng-container *ngIf="!last">
|
|
9
|
+
<span class="inline-flex h-6 w-6 items-center justify-center text-[#737373]" aria-hidden="true">
|
|
10
|
+
<svg
|
|
11
|
+
*ngIf="mode === 'custom-separator' && item !== '...'"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
class="h-4 w-4"
|
|
14
|
+
fill="none"
|
|
15
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
+
>
|
|
17
|
+
<path d="M8 20L16 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
18
|
+
</svg>
|
|
19
|
+
<svg
|
|
20
|
+
*ngIf="(mode !== 'custom-separator' && item !== '...') || item === '...'"
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
class="h-4 w-4"
|
|
23
|
+
fill="none"
|
|
24
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
25
|
+
>
|
|
26
|
+
<path d="M9 6L15 12L9 18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
27
|
+
</svg>
|
|
28
|
+
</span>
|
|
29
|
+
</ng-container>
|
|
30
|
+
|
|
31
|
+
<ng-container *ngIf="mode === 'dropdown' && i === 1 && item !== '...' && !last">
|
|
32
|
+
<span class="-ml-2 inline-flex h-6 w-6 items-center justify-center text-[#737373]" aria-hidden="true">
|
|
33
|
+
<svg viewBox="0 0 24 24" class="h-4 w-4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
34
|
+
<path d="M7 10L12 15L17 10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
35
|
+
</svg>
|
|
36
|
+
</span>
|
|
37
|
+
</ng-container>
|
|
38
|
+
</ng-container>
|
|
39
|
+
</nav>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type PdmBreadcrumbMode =
|
|
4
|
+
| 'custom-separator'
|
|
5
|
+
| 'dropdown'
|
|
6
|
+
| 'collapsed'
|
|
7
|
+
| 'link-component'
|
|
8
|
+
| 'responsive';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'pdm-breadcrumb',
|
|
12
|
+
templateUrl: './breadcrumb.component.html',
|
|
13
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
14
|
+
})
|
|
15
|
+
export class PdmBreadcrumbComponent {
|
|
16
|
+
@Input() mode: PdmBreadcrumbMode = 'link-component';
|
|
17
|
+
@Input() items: string[] = ['Home', 'Components', 'Breadcrumb'];
|
|
18
|
+
@Input() className = '';
|
|
19
|
+
|
|
20
|
+
get renderedItems(): string[] {
|
|
21
|
+
if ((this.mode === 'collapsed' || this.mode === 'responsive') && this.items.length > 3) {
|
|
22
|
+
return [this.items[0], '...', this.items[this.items.length - 2], this.items[this.items.length - 1]];
|
|
23
|
+
}
|
|
24
|
+
return this.items;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<button
|
|
2
|
+
[type]="type"
|
|
3
|
+
[disabled]="isDisabled"
|
|
4
|
+
[attr.aria-busy]="loading"
|
|
5
|
+
[ngClass]="rootClasses"
|
|
6
|
+
(click)="onClick($event)"
|
|
7
|
+
>
|
|
8
|
+
<span
|
|
9
|
+
*ngIf="loading"
|
|
10
|
+
class="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-r-transparent"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
></span>
|
|
13
|
+
<ng-content select="[pdmButtonIcon]" *ngIf="variant === 'with-icon' || variant === 'icon' || variant === 'icon-circle' || variant === 'rounded'"></ng-content>
|
|
14
|
+
<ng-content></ng-content>
|
|
15
|
+
</button>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type PdmButtonVariant =
|
|
4
|
+
| 'default'
|
|
5
|
+
| 'primary'
|
|
6
|
+
| 'destructive'
|
|
7
|
+
| 'outline'
|
|
8
|
+
| 'subtle'
|
|
9
|
+
| 'secondary'
|
|
10
|
+
| 'ghost'
|
|
11
|
+
| 'link'
|
|
12
|
+
| 'with-icon'
|
|
13
|
+
| 'icon'
|
|
14
|
+
| 'icon-circle'
|
|
15
|
+
| 'rounded'
|
|
16
|
+
| 'loading';
|
|
17
|
+
export type PdmButtonState = 'default' | 'hover';
|
|
18
|
+
export type PdmButtonSize = 'small' | 'default' | 'large';
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'pdm-button',
|
|
22
|
+
templateUrl: './button.component.html',
|
|
23
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
24
|
+
})
|
|
25
|
+
export class PdmButtonComponent {
|
|
26
|
+
@Input() type: 'button' | 'submit' | 'reset' = 'button';
|
|
27
|
+
@Input() variant: PdmButtonVariant = 'default';
|
|
28
|
+
@Input() state: PdmButtonState = 'default';
|
|
29
|
+
@Input() size: PdmButtonSize = 'default';
|
|
30
|
+
@Input() disabled = false;
|
|
31
|
+
@Input() loading = false;
|
|
32
|
+
@Input() className = '';
|
|
33
|
+
|
|
34
|
+
@Output() pressed = new EventEmitter<MouseEvent>();
|
|
35
|
+
|
|
36
|
+
readonly toneClassMap: Record<PdmButtonVariant, { default: string; hover: string }> = {
|
|
37
|
+
default: { default: 'bg-primary text-primary-foreground', hover: 'bg-primary text-primary-foreground opacity-90' },
|
|
38
|
+
primary: { default: 'bg-primary text-primary-foreground', hover: 'bg-primary text-primary-foreground opacity-90' },
|
|
39
|
+
destructive: { default: 'bg-destructive text-background', hover: 'bg-destructive text-background opacity-90' },
|
|
40
|
+
outline: { default: 'border border-border bg-background text-foreground', hover: 'border border-border bg-accent text-accent-foreground' },
|
|
41
|
+
subtle: { default: 'bg-secondary text-secondary-foreground', hover: 'bg-accent text-accent-foreground' },
|
|
42
|
+
secondary: { default: 'bg-secondary text-secondary-foreground', hover: 'bg-accent text-accent-foreground' },
|
|
43
|
+
ghost: { default: 'bg-transparent text-foreground', hover: 'bg-accent text-accent-foreground' },
|
|
44
|
+
link: { default: 'bg-transparent text-primary', hover: 'bg-transparent text-primary' },
|
|
45
|
+
'with-icon': { default: 'bg-primary text-primary-foreground', hover: 'bg-primary text-primary-foreground opacity-90' },
|
|
46
|
+
icon: { default: 'border border-border bg-background text-foreground', hover: 'border border-border bg-accent text-accent-foreground' },
|
|
47
|
+
'icon-circle': { default: 'border border-border bg-background text-foreground', hover: 'border border-border bg-accent text-accent-foreground' },
|
|
48
|
+
rounded: { default: 'border border-border bg-background text-foreground', hover: 'border border-border bg-accent text-accent-foreground' },
|
|
49
|
+
loading: { default: 'bg-primary text-primary-foreground opacity-70', hover: 'bg-primary text-primary-foreground opacity-70' }
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
get isDisabled(): boolean {
|
|
53
|
+
return this.disabled || this.loading || this.variant === 'loading';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get rootClasses(): string[] {
|
|
57
|
+
const currentState = this.state === 'hover' ? 'hover' : 'default';
|
|
58
|
+
const toneClass = this.toneClassMap[this.variant][currentState];
|
|
59
|
+
|
|
60
|
+
return [
|
|
61
|
+
'inline-flex items-center justify-center gap-2 rounded-[6px] border border-transparent text-[14px] font-medium leading-6 transition-colors outline-none focus:outline-none focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
|
|
62
|
+
this.variant === 'link' ? 'px-4 py-2 h-9' : '',
|
|
63
|
+
this.variant === 'icon' ? 'h-8 w-8 p-2' : '',
|
|
64
|
+
this.variant === 'icon-circle' ? 'h-10 w-10 rounded-[96px] p-3' : '',
|
|
65
|
+
this.variant === 'rounded' ? 'h-9 w-9 rounded-full p-0' : '',
|
|
66
|
+
this.variant !== 'icon' && this.variant !== 'icon-circle' && this.variant !== 'rounded'
|
|
67
|
+
? this.size === 'small'
|
|
68
|
+
? 'h-8 px-3'
|
|
69
|
+
: this.size === 'large'
|
|
70
|
+
? 'h-10 px-5'
|
|
71
|
+
: 'h-9 px-4'
|
|
72
|
+
: '',
|
|
73
|
+
this.variant === 'link' && this.state === 'hover' ? 'underline [text-decoration-skip-ink:none]' : '',
|
|
74
|
+
toneClass,
|
|
75
|
+
this.className
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onClick(event: MouseEvent): void {
|
|
80
|
+
if (!this.isDisabled) {
|
|
81
|
+
this.pressed.emit(event);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<div [ngClass]="['inline-flex items-center gap-2', className]">
|
|
2
|
+
<ng-container [ngSwitch]="variant">
|
|
3
|
+
<ng-container *ngSwitchCase="'orientation'">
|
|
4
|
+
<div class="inline-flex flex-col overflow-hidden rounded-[8px] border border-[#e5e5e5] bg-white shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">
|
|
5
|
+
<button type="button" class="inline-flex h-9 w-9 items-center justify-center border-b border-[#e5e5e5] text-[#0a0a0a]">+</button>
|
|
6
|
+
<button type="button" class="inline-flex h-9 w-9 items-center justify-center text-[#0a0a0a]">-</button>
|
|
7
|
+
</div>
|
|
8
|
+
</ng-container>
|
|
9
|
+
|
|
10
|
+
<ng-container *ngSwitchCase="'separator'">
|
|
11
|
+
<div class="inline-flex overflow-hidden rounded-[8px] border border-[#e5e5e5] bg-[#f5f5f5] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">
|
|
12
|
+
<button type="button" class="inline-flex h-8 items-center justify-center border-r border-[#e5e5e5] px-3 text-[14px] font-medium leading-5 text-[#0a0a0a]">Copy</button>
|
|
13
|
+
<button type="button" class="inline-flex h-8 items-center justify-center px-3 text-[14px] font-medium leading-5 text-[#0a0a0a]">Paste</button>
|
|
14
|
+
</div>
|
|
15
|
+
</ng-container>
|
|
16
|
+
|
|
17
|
+
<ng-container *ngSwitchCase="'nested'">
|
|
18
|
+
<div class="inline-flex overflow-hidden rounded-[8px] border border-[#e5e5e5] bg-white shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">
|
|
19
|
+
<button *ngFor="let item of ['1', '2', '3', '4', '5']; let last = last" type="button" [ngClass]="['inline-flex h-8 items-center justify-center px-3 text-[14px] font-medium leading-5 text-[#0a0a0a]', !last ? 'border-r border-[#e5e5e5]' : '']">{{ item }}</button>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="inline-flex overflow-hidden rounded-[8px] border border-[#e5e5e5] bg-white shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">
|
|
22
|
+
<button type="button" class="inline-flex h-8 w-8 items-center justify-center border-r border-[#e5e5e5] text-[#0a0a0a]">←</button>
|
|
23
|
+
<button type="button" class="inline-flex h-8 w-8 items-center justify-center text-[#0a0a0a]">→</button>
|
|
24
|
+
</div>
|
|
25
|
+
</ng-container>
|
|
26
|
+
|
|
27
|
+
<ng-container *ngSwitchDefault>
|
|
28
|
+
<div class="inline-flex h-9 w-9 items-center justify-center overflow-hidden rounded-[8px] border border-[#e5e5e5] bg-white shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)] text-[#0a0a0a]">←</div>
|
|
29
|
+
<div class="inline-flex overflow-hidden rounded-[8px] border border-[#e5e5e5] bg-white shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">
|
|
30
|
+
<button *ngFor="let item of items; let last = last" type="button" [ngClass]="['inline-flex h-9 items-center justify-center px-4 text-[14px] font-medium leading-5 text-[#0a0a0a]', !last ? 'border-r border-[#e5e5e5]' : '']">{{ item }}</button>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="inline-flex overflow-hidden rounded-[8px] border border-[#e5e5e5] bg-white shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">
|
|
33
|
+
<button type="button" class="inline-flex h-9 items-center justify-center border-r border-[#e5e5e5] px-4 text-[14px] font-medium leading-5 text-[#0a0a0a]">Snooze</button>
|
|
34
|
+
<button type="button" class="inline-flex h-9 w-9 items-center justify-center text-[#0a0a0a]">…</button>
|
|
35
|
+
</div>
|
|
36
|
+
</ng-container>
|
|
37
|
+
</ng-container>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type PdmButtonGroupVariant = 'default' | 'orientation' | 'separator' | 'nested';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'pdm-button-group',
|
|
7
|
+
templateUrl: './button-group.component.html',
|
|
8
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
9
|
+
})
|
|
10
|
+
export class PdmButtonGroupComponent {
|
|
11
|
+
@Input() variant: PdmButtonGroupVariant = 'default';
|
|
12
|
+
@Input() items: string[] = ['Archive', 'Report'];
|
|
13
|
+
@Input() className = '';
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<section
|
|
2
|
+
[ngClass]="[
|
|
3
|
+
'rounded-[10px] border border-[#e5e5e5] bg-white p-3 shadow-[0px_1px_3px_0px_rgba(0,0,0,0.1)]',
|
|
4
|
+
mode === 'range' ? 'w-[488px]' : 'w-[250px]',
|
|
5
|
+
className
|
|
6
|
+
]"
|
|
7
|
+
>
|
|
8
|
+
<div [ngClass]="['grid gap-4', mode === 'range' ? 'grid-cols-2' : 'grid-cols-1']">
|
|
9
|
+
<div class="space-y-4">
|
|
10
|
+
<header class="flex items-center justify-between">
|
|
11
|
+
<button type="button" class="inline-flex h-8 w-8 items-center justify-center rounded-[6px] text-[#0a0a0a]">‹</button>
|
|
12
|
+
<div [ngClass]="['flex items-center justify-center gap-1', mode === 'range' ? 'w-full' : 'w-[160px]']">
|
|
13
|
+
<ng-container *ngIf="mode === 'single'; else monthText">
|
|
14
|
+
<span class="inline-flex h-8 items-center rounded-[8px] border border-[#e5e5e5] px-2 text-[14px] font-medium leading-5 text-[#0a0a0a] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">Jun</span>
|
|
15
|
+
<span class="inline-flex h-8 items-center rounded-[8px] border border-[#e5e5e5] px-2 text-[14px] font-medium leading-5 text-[#0a0a0a] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.1)]">2025</span>
|
|
16
|
+
</ng-container>
|
|
17
|
+
<ng-template #monthText>
|
|
18
|
+
<span class="text-[28px] text-[14px] font-medium leading-5 text-[#0a0a0a]">{{ leftMonthName }} {{ year }}</span>
|
|
19
|
+
</ng-template>
|
|
20
|
+
</div>
|
|
21
|
+
<button type="button" class="inline-flex h-8 w-8 items-center justify-center rounded-[6px] text-[#0a0a0a]">{{ mode === 'range' ? '' : '›' }}</button>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<div>
|
|
25
|
+
<div class="grid grid-cols-7">
|
|
26
|
+
<span *ngFor="let day of weekdays" class="inline-flex h-[21px] items-center justify-center text-[12px] font-normal leading-4 text-[#737373]">{{ day }}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="mt-2 grid grid-cols-7 gap-y-2">
|
|
29
|
+
<button
|
|
30
|
+
*ngFor="let cell of leftCells"
|
|
31
|
+
type="button"
|
|
32
|
+
[ngClass]="[
|
|
33
|
+
'inline-flex h-8 w-8 items-center justify-center rounded-[8px] text-[14px] font-normal leading-5',
|
|
34
|
+
cell.outside ? 'text-[#0a0a0a] opacity-50' : 'text-[#0a0a0a]',
|
|
35
|
+
cell.inRange ? 'bg-[#f5f5f5]' : '',
|
|
36
|
+
cell.selected ? 'bg-[#171717] text-[#fafafa]' : ''
|
|
37
|
+
]"
|
|
38
|
+
>
|
|
39
|
+
{{ cell.day }}
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div *ngIf="mode === 'range'" class="space-y-4">
|
|
46
|
+
<header class="flex items-center justify-between">
|
|
47
|
+
<span class="text-[14px] font-medium leading-5 text-[#0a0a0a]">{{ rightMonthName }} {{ month === 12 ? year + 1 : year }}</span>
|
|
48
|
+
<button type="button" class="inline-flex h-8 w-8 items-center justify-center rounded-[6px] text-[#0a0a0a]">›</button>
|
|
49
|
+
</header>
|
|
50
|
+
|
|
51
|
+
<div>
|
|
52
|
+
<div class="grid grid-cols-7">
|
|
53
|
+
<span *ngFor="let day of weekdays" class="inline-flex h-[21px] items-center justify-center text-[12px] font-normal leading-4 text-[#737373]">{{ day }}</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="mt-2 grid grid-cols-7 gap-y-2">
|
|
56
|
+
<button
|
|
57
|
+
*ngFor="let cell of rightCells"
|
|
58
|
+
type="button"
|
|
59
|
+
[ngClass]="[
|
|
60
|
+
'inline-flex h-8 w-8 items-center justify-center rounded-[8px] text-[14px] font-normal leading-5',
|
|
61
|
+
cell.outside ? 'text-[#0a0a0a] opacity-50' : 'text-[#0a0a0a]',
|
|
62
|
+
cell.inRange ? 'bg-[#f5f5f5]' : '',
|
|
63
|
+
cell.selected ? 'bg-[#171717] text-[#fafafa]' : ''
|
|
64
|
+
]"
|
|
65
|
+
>
|
|
66
|
+
{{ cell.day }}
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type PdmCalendarMode = 'single' | 'range';
|
|
4
|
+
|
|
5
|
+
interface CalendarCell {
|
|
6
|
+
date: Date;
|
|
7
|
+
day: number;
|
|
8
|
+
outside: boolean;
|
|
9
|
+
selected: boolean;
|
|
10
|
+
inRange: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'pdm-calendar',
|
|
15
|
+
templateUrl: './calendar.component.html',
|
|
16
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
17
|
+
})
|
|
18
|
+
export class PdmCalendarComponent {
|
|
19
|
+
@Input() mode: PdmCalendarMode = 'single';
|
|
20
|
+
@Input() month = 6;
|
|
21
|
+
@Input() year = 2025;
|
|
22
|
+
@Input() selectedDay = 25;
|
|
23
|
+
@Input() rangeStartDay = 25;
|
|
24
|
+
@Input() rangeEndDay = 9;
|
|
25
|
+
@Input() className = '';
|
|
26
|
+
|
|
27
|
+
readonly weekdays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
28
|
+
|
|
29
|
+
get leftMonthName(): string {
|
|
30
|
+
return this.monthName(this.month - 1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get rightMonthName(): string {
|
|
34
|
+
const rightMonth = this.month % 12;
|
|
35
|
+
return this.monthName(rightMonth);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get leftCells(): CalendarCell[] {
|
|
39
|
+
return this.buildMonthCells(this.year, this.month - 1, this.selectedDay, this.mode === 'range' ? this.rangeStartDay : undefined, this.mode === 'range' ? 31 : undefined);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get rightCells(): CalendarCell[] {
|
|
43
|
+
const rightMonth = this.month % 12;
|
|
44
|
+
const rightYear = this.month === 12 ? this.year + 1 : this.year;
|
|
45
|
+
return this.buildMonthCells(rightYear, rightMonth, this.mode === 'range' ? this.rangeEndDay : undefined, 1, this.mode === 'range' ? this.rangeEndDay : undefined);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private monthName(monthIndex: number): string {
|
|
49
|
+
const safe = ((monthIndex % 12) + 12) % 12;
|
|
50
|
+
return new Date(this.year, safe, 1).toLocaleString('en-US', { month: 'long' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private buildMonthCells(
|
|
54
|
+
year: number,
|
|
55
|
+
monthIndex: number,
|
|
56
|
+
selectedDay?: number,
|
|
57
|
+
rangeStartDay?: number,
|
|
58
|
+
rangeEndDay?: number
|
|
59
|
+
): CalendarCell[] {
|
|
60
|
+
const firstDay = new Date(year, monthIndex, 1);
|
|
61
|
+
const startWeekday = firstDay.getDay();
|
|
62
|
+
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
|
|
63
|
+
const cells: CalendarCell[] = [];
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < 42; i += 1) {
|
|
66
|
+
const dayNumber = i - startWeekday + 1;
|
|
67
|
+
const date = new Date(year, monthIndex, dayNumber);
|
|
68
|
+
const outside = dayNumber < 1 || dayNumber > daysInMonth;
|
|
69
|
+
const day = date.getDate();
|
|
70
|
+
const selected = !outside && !!selectedDay && day === selectedDay;
|
|
71
|
+
const inRange = !outside && !!rangeStartDay && !!rangeEndDay && day >= rangeStartDay && day <= rangeEndDay;
|
|
72
|
+
cells.push({ date, day, outside, selected, inRange });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return cells;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<section
|
|
2
|
+
[ngClass]="[
|
|
3
|
+
'w-full rounded-[14px] border border-[#e5e5e5] bg-white py-6 shadow-[0_1px_3px_rgba(0,0,0,0.1)]',
|
|
4
|
+
className
|
|
5
|
+
]"
|
|
6
|
+
>
|
|
7
|
+
<ng-container *ngIf="variant === 'login'; else defaultCard">
|
|
8
|
+
<div class="flex w-full items-start gap-1.5 px-6">
|
|
9
|
+
<div class="min-w-0 flex-1">
|
|
10
|
+
<h3 class="m-0 text-[14px] font-semibold leading-5 text-[#0a0a0a]">{{ title }}</h3>
|
|
11
|
+
<p class="m-0 mt-1.5 text-[14px] leading-5 text-[#737373]">{{ description }}</p>
|
|
12
|
+
</div>
|
|
13
|
+
<button
|
|
14
|
+
type="button"
|
|
15
|
+
class="inline-flex h-9 items-center justify-center rounded-[10px] px-4 py-2 text-[14px] font-medium leading-5 text-[#171717]"
|
|
16
|
+
(click)="onActionPressed()"
|
|
17
|
+
>
|
|
18
|
+
{{ actionText }}
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="mt-6 flex flex-col gap-4 px-6">
|
|
23
|
+
<div class="w-full">
|
|
24
|
+
<label class="mb-3 block text-[14px] font-medium leading-5 text-[#0a0a0a]">{{ emailLabel }}</label>
|
|
25
|
+
<input
|
|
26
|
+
type="email"
|
|
27
|
+
[placeholder]="emailPlaceholder"
|
|
28
|
+
class="h-9 w-full rounded-[8px] border border-[#e5e5e5] bg-white px-3 py-1 text-[16px] leading-6 text-[#0a0a0a] shadow-[0_1px_2px_rgba(0,0,0,0.1)] placeholder:text-[#737373] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#171717]"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="w-full">
|
|
33
|
+
<div class="mb-3 flex w-full items-center justify-between gap-4">
|
|
34
|
+
<label class="text-[14px] font-medium leading-5 text-[#0a0a0a]">{{ passwordLabel }}</label>
|
|
35
|
+
<button type="button" class="text-[14px] leading-5 text-[#0a0a0a]">
|
|
36
|
+
{{ passwordHint }}
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
<input
|
|
40
|
+
type="password"
|
|
41
|
+
class="h-9 w-full rounded-[8px] border border-[#e5e5e5] bg-white px-3 py-1 text-[16px] leading-6 text-[#0a0a0a] shadow-[0_1px_2px_rgba(0,0,0,0.1)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#171717]"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="mt-6 flex w-full flex-col gap-2 px-6">
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
class="flex h-9 w-full items-center justify-center rounded-[10px] bg-[#171717] px-4 py-2 text-[14px] font-medium leading-5 text-[#fafafa] shadow-[0_1px_2px_rgba(0,0,0,0.1)]"
|
|
50
|
+
(click)="onPrimaryAction()"
|
|
51
|
+
>
|
|
52
|
+
{{ primaryActionText }}
|
|
53
|
+
</button>
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
class="flex h-9 w-full items-center justify-center rounded-[10px] border border-[#e5e5e5] bg-white px-4 py-2 text-[14px] font-medium leading-5 text-[#0a0a0a] shadow-[0_1px_2px_rgba(0,0,0,0.1)]"
|
|
57
|
+
(click)="onSecondaryAction()"
|
|
58
|
+
>
|
|
59
|
+
{{ secondaryActionText }}
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
</ng-container>
|
|
63
|
+
|
|
64
|
+
<ng-template #defaultCard>
|
|
65
|
+
<div class="flex w-full flex-col gap-6">
|
|
66
|
+
<div class="px-6">
|
|
67
|
+
<ng-content select="[pdmCardHeader]"></ng-content>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="px-6">
|
|
70
|
+
<ng-content select="[pdmCardContent]"></ng-content>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="px-6">
|
|
73
|
+
<ng-content select="[pdmCardFooter]"></ng-content>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</ng-template>
|
|
77
|
+
</section>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type PdmCardVariant = 'default' | 'login';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'pdm-card',
|
|
7
|
+
templateUrl: './card.component.html',
|
|
8
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
9
|
+
})
|
|
10
|
+
export class PdmCardComponent {
|
|
11
|
+
@Input() variant: PdmCardVariant = 'default';
|
|
12
|
+
@Input() className = '';
|
|
13
|
+
|
|
14
|
+
@Input() title = 'Login to your account';
|
|
15
|
+
@Input() description = 'Enter your email below to login to your account';
|
|
16
|
+
@Input() actionText = 'Sign up';
|
|
17
|
+
@Input() emailLabel = 'Email';
|
|
18
|
+
@Input() emailPlaceholder = 'm@example.com';
|
|
19
|
+
@Input() passwordLabel = 'Password';
|
|
20
|
+
@Input() passwordHint = 'Forgot password?';
|
|
21
|
+
@Input() primaryActionText = 'Login';
|
|
22
|
+
@Input() secondaryActionText = 'Login with Google';
|
|
23
|
+
|
|
24
|
+
@Output() primaryAction = new EventEmitter<void>();
|
|
25
|
+
@Output() secondaryAction = new EventEmitter<void>();
|
|
26
|
+
@Output() actionPressed = new EventEmitter<void>();
|
|
27
|
+
|
|
28
|
+
onPrimaryAction(): void {
|
|
29
|
+
this.primaryAction.emit();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onSecondaryAction(): void {
|
|
33
|
+
this.secondaryAction.emit();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onActionPressed(): void {
|
|
37
|
+
this.actionPressed.emit();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<section [ngClass]="[className]">
|
|
2
|
+
<div *ngIf="variant !== 'orientation'" class="flex items-center gap-4">
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-[#e5e5e5] bg-white p-[10px]"
|
|
6
|
+
[ngClass]="!canPrev ? 'opacity-50' : ''"
|
|
7
|
+
[disabled]="!canPrev"
|
|
8
|
+
(click)="onPrev()"
|
|
9
|
+
aria-label="Previous slide"
|
|
10
|
+
>
|
|
11
|
+
<svg viewBox="0 0 24 24" class="h-4 w-4 text-[#0a0a0a]" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
12
|
+
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
13
|
+
</svg>
|
|
14
|
+
</button>
|
|
15
|
+
|
|
16
|
+
<ng-container [ngSwitch]="variant">
|
|
17
|
+
<div *ngSwitchCase="'sizes'" class="flex items-center">
|
|
18
|
+
<div *ngFor="let slide of visibleSlides; let i = index" class="flex items-center" [ngClass]="i > 0 ? 'pl-4' : ''">
|
|
19
|
+
<div class="flex h-[165px] w-[125px] items-center p-1">
|
|
20
|
+
<div class="flex h-full w-full items-center justify-center rounded-[14px] border border-[#e5e5e5] bg-white py-6 shadow-[0_1px_3px_rgba(0,0,0,0.1)]">
|
|
21
|
+
<div class="text-center text-[30px] font-semibold leading-9 text-[#0a0a0a]">{{ slide }}</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div *ngSwitchDefault class="flex flex-col items-center justify-center">
|
|
28
|
+
<div class="flex h-[360px] w-[318px] items-center p-1">
|
|
29
|
+
<div class="flex h-full w-full items-center justify-center rounded-[14px] border border-[#e5e5e5] bg-white py-6 shadow-[0_1px_3px_rgba(0,0,0,0.1)]">
|
|
30
|
+
<div class="text-center text-[36px] font-semibold leading-10 text-[#0a0a0a]">{{ visibleSlides[0] }}</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<p *ngIf="variant === 'api'" class="m-0 pt-2 text-[14px] font-medium leading-5 text-[#737373]">Slide {{ index + 1 }} of {{ slides.length }}</p>
|
|
34
|
+
</div>
|
|
35
|
+
</ng-container>
|
|
36
|
+
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-[#e5e5e5] bg-white p-[10px]"
|
|
40
|
+
[ngClass]="!canNext ? 'opacity-50' : ''"
|
|
41
|
+
[disabled]="!canNext"
|
|
42
|
+
(click)="onNext()"
|
|
43
|
+
aria-label="Next slide"
|
|
44
|
+
>
|
|
45
|
+
<svg viewBox="0 0 24 24" class="h-4 w-4 text-[#0a0a0a]" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
46
|
+
<path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div *ngIf="variant === 'orientation'" class="flex flex-col items-center gap-4">
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-[#e5e5e5] bg-white p-[10px]"
|
|
55
|
+
[ngClass]="!canPrev ? 'opacity-50' : ''"
|
|
56
|
+
[disabled]="!canPrev"
|
|
57
|
+
(click)="onPrev()"
|
|
58
|
+
aria-label="Previous slide"
|
|
59
|
+
>
|
|
60
|
+
<svg viewBox="0 0 24 24" class="h-4 w-4 text-[#0a0a0a]" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
61
|
+
<path d="M18 15L12 9L6 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
62
|
+
</svg>
|
|
63
|
+
</button>
|
|
64
|
+
|
|
65
|
+
<div class="flex w-[320px] flex-col">
|
|
66
|
+
<div *ngFor="let slide of visibleSlides; let i = index" class="flex h-[142px] w-full items-center p-1" [ngClass]="i > 0 ? 'pt-1' : ''">
|
|
67
|
+
<div class="flex h-full w-full items-center justify-center rounded-[14px] border border-[#e5e5e5] bg-white py-6 shadow-[0_1px_3px_rgba(0,0,0,0.1)]">
|
|
68
|
+
<div class="text-center text-[30px] font-semibold leading-9 text-[#0a0a0a]">{{ slide }}</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-[#e5e5e5] bg-white p-[10px]"
|
|
76
|
+
[ngClass]="!canNext ? 'opacity-50' : ''"
|
|
77
|
+
[disabled]="!canNext"
|
|
78
|
+
(click)="onNext()"
|
|
79
|
+
aria-label="Next slide"
|
|
80
|
+
>
|
|
81
|
+
<svg viewBox="0 0 24 24" class="h-4 w-4 text-[#0a0a0a]" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
82
|
+
<path d="M6 9L12 15L18 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</section>
|