ngx-opalbytes-components 1.19.0 → 1.21.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 (95) hide show
  1. package/README.md +103 -0
  2. package/cli/cao-comp.cjs +456 -0
  3. package/fesm2022/ngx-opalbytes-components.mjs +23 -7
  4. package/fesm2022/ngx-opalbytes-components.mjs.map +1 -1
  5. package/package.json +4 -1
  6. package/src/lib/shared/components/autocomplete/autocomplete.component.html +126 -0
  7. package/src/lib/shared/components/autocomplete/autocomplete.component.scss +150 -0
  8. package/src/lib/shared/components/autocomplete/autocomplete.component.ts +552 -0
  9. package/src/lib/shared/components/autocomplete/autocomplete.stories.ts +133 -0
  10. package/src/lib/shared/components/base-button/base-button.component.html +33 -0
  11. package/src/lib/shared/components/base-button/base-button.component.scss +14 -0
  12. package/src/lib/shared/components/base-button/base-button.component.spec.ts +64 -0
  13. package/src/lib/shared/components/base-button/base-button.component.ts +48 -0
  14. package/src/lib/shared/components/base-button/base-button.stories.ts +102 -0
  15. package/src/lib/shared/components/base-icon/base-icon.css +0 -0
  16. package/src/lib/shared/components/base-icon/base-icon.html +11 -0
  17. package/src/lib/shared/components/base-icon/base-icon.spec.ts +23 -0
  18. package/src/lib/shared/components/base-icon/base-icon.stories.ts +56 -0
  19. package/src/lib/shared/components/base-icon/base-icon.ts +18 -0
  20. package/src/lib/shared/components/base-input/base-input.css +193 -0
  21. package/src/lib/shared/components/base-input/base-input.html +43 -0
  22. package/src/lib/shared/components/base-input/base-input.spec.ts +146 -0
  23. package/src/lib/shared/components/base-input/base-input.stories.ts +137 -0
  24. package/src/lib/shared/components/base-input/base-input.ts +133 -0
  25. package/src/lib/shared/components/base-time-range/base-time-range.css +27 -0
  26. package/src/lib/shared/components/base-time-range/base-time-range.html +23 -0
  27. package/src/lib/shared/components/base-time-range/base-time-range.spec.ts +27 -0
  28. package/src/lib/shared/components/base-time-range/base-time-range.stories.ts +54 -0
  29. package/src/lib/shared/components/base-time-range/base-time-range.ts +61 -0
  30. package/src/lib/shared/components/base-toggle/base-toggle.component.html +28 -0
  31. package/src/lib/shared/components/base-toggle/base-toggle.component.scss +45 -0
  32. package/src/lib/shared/components/base-toggle/base-toggle.component.spec.ts +22 -0
  33. package/src/lib/shared/components/base-toggle/base-toggle.component.ts +138 -0
  34. package/src/lib/shared/components/base-toggle/base-toggle.stories.ts +63 -0
  35. package/src/lib/shared/components/card/card.component.css +264 -0
  36. package/src/lib/shared/components/card/card.component.html +103 -0
  37. package/src/lib/shared/components/card/card.component.ts +63 -0
  38. package/src/lib/shared/components/card/card.stories.ts +114 -0
  39. package/src/lib/shared/components/date-picker/date-picker.html +82 -0
  40. package/src/lib/shared/components/date-picker/date-picker.scss +8 -0
  41. package/src/lib/shared/components/date-picker/date-picker.spec.ts +23 -0
  42. package/src/lib/shared/components/date-picker/date-picker.stories.ts +91 -0
  43. package/src/lib/shared/components/date-picker/date-picker.ts +165 -0
  44. package/src/lib/shared/components/drop-down/drop-down.css +61 -0
  45. package/src/lib/shared/components/drop-down/drop-down.html +96 -0
  46. package/src/lib/shared/components/drop-down/drop-down.spec.ts +335 -0
  47. package/src/lib/shared/components/drop-down/drop-down.stories.ts +90 -0
  48. package/src/lib/shared/components/drop-down/drop-down.ts +335 -0
  49. package/src/lib/shared/components/footer/footer.css +29 -0
  50. package/src/lib/shared/components/footer/footer.html +10 -0
  51. package/src/lib/shared/components/footer/footer.spec.ts +22 -0
  52. package/src/lib/shared/components/footer/footer.stories.ts +51 -0
  53. package/src/lib/shared/components/footer/footer.ts +15 -0
  54. package/src/lib/shared/components/index.ts +19 -0
  55. package/src/lib/shared/components/kpi/kpi.css +0 -0
  56. package/src/lib/shared/components/kpi/kpi.html +11 -0
  57. package/src/lib/shared/components/kpi/kpi.spec.ts +38 -0
  58. package/src/lib/shared/components/kpi/kpi.stories.ts +65 -0
  59. package/src/lib/shared/components/kpi/kpi.ts +25 -0
  60. package/src/lib/shared/components/links-button/links-button.css +70 -0
  61. package/src/lib/shared/components/links-button/links-button.html +23 -0
  62. package/src/lib/shared/components/links-button/links-button.spec.ts +139 -0
  63. package/src/lib/shared/components/links-button/links-button.stories.ts +75 -0
  64. package/src/lib/shared/components/links-button/links-button.ts +45 -0
  65. package/src/lib/shared/components/paginator/paginator.css +0 -0
  66. package/src/lib/shared/components/paginator/paginator.html +10 -0
  67. package/src/lib/shared/components/paginator/paginator.spec.ts +23 -0
  68. package/src/lib/shared/components/paginator/paginator.stories.ts +56 -0
  69. package/src/lib/shared/components/paginator/paginator.ts +25 -0
  70. package/src/lib/shared/components/stepper/step/step.css +8 -0
  71. package/src/lib/shared/components/stepper/step/step.html +1 -0
  72. package/src/lib/shared/components/stepper/step/step.spec.ts +131 -0
  73. package/src/lib/shared/components/stepper/step/step.ts +14 -0
  74. package/src/lib/shared/components/stepper/stepper.css +17 -0
  75. package/src/lib/shared/components/stepper/stepper.html +11 -0
  76. package/src/lib/shared/components/stepper/stepper.spec.ts +238 -0
  77. package/src/lib/shared/components/stepper/stepper.stories.ts +77 -0
  78. package/src/lib/shared/components/stepper/stepper.ts +57 -0
  79. package/src/lib/shared/components/tab-group/tab-group.component.html +84 -0
  80. package/src/lib/shared/components/tab-group/tab-group.component.scss +151 -0
  81. package/src/lib/shared/components/tab-group/tab-group.spec.ts +22 -0
  82. package/src/lib/shared/components/tab-group/tab-group.stories.ts +102 -0
  83. package/src/lib/shared/components/tab-group/tab-group.ts +218 -0
  84. package/src/lib/shared/components/tab-panel/tab-content.directive.ts +7 -0
  85. package/src/lib/shared/components/tab-panel/tab-label.directive.ts +7 -0
  86. package/src/lib/shared/components/tab-panel/tab-panel.css +0 -0
  87. package/src/lib/shared/components/tab-panel/tab-panel.spec.ts +22 -0
  88. package/src/lib/shared/components/tab-panel/tab-panel.stories.ts +54 -0
  89. package/src/lib/shared/components/tab-panel/tab-panel.ts +31 -0
  90. package/src/lib/shared/components/time-picker/time-picker.css +84 -0
  91. package/src/lib/shared/components/time-picker/time-picker.html +21 -0
  92. package/src/lib/shared/components/time-picker/time-picker.spec.ts +23 -0
  93. package/src/lib/shared/components/time-picker/time-picker.stories.ts +66 -0
  94. package/src/lib/shared/components/time-picker/time-picker.ts +90 -0
  95. package/types/ngx-opalbytes-components.d.ts +23 -4
@@ -0,0 +1,335 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import {
3
+ Component,
4
+ ElementRef,
5
+ EventEmitter,
6
+ Input,
7
+ Output,
8
+ ViewEncapsulation,
9
+ forwardRef,
10
+ OnInit,
11
+ OnDestroy,
12
+ ChangeDetectorRef,
13
+ } from "@angular/core";
14
+ import {
15
+ AbstractControl,
16
+ ControlValueAccessor,
17
+ NG_VALUE_ACCESSOR,
18
+ ReactiveFormsModule,
19
+ FormControl,
20
+ } from "@angular/forms";
21
+ import { MatIconModule } from "@angular/material/icon";
22
+ import { MatTooltipModule } from "@angular/material/tooltip";
23
+
24
+ export interface ICaoDropDownOption {
25
+ id?: string | number;
26
+ nome: string;
27
+ icon?: string;
28
+ iconPosition?: string;
29
+ dataCy?: string;
30
+ }
31
+
32
+ @Component({
33
+ selector: "cao-drop-down",
34
+ templateUrl: "./drop-down.html",
35
+ styleUrl: "./drop-down.css",
36
+ encapsulation: ViewEncapsulation.None,
37
+ imports: [
38
+ MatIconModule,
39
+ CommonModule,
40
+ MatTooltipModule,
41
+ ReactiveFormsModule,
42
+ ],
43
+ providers: [
44
+ {
45
+ provide: NG_VALUE_ACCESSOR,
46
+ useExisting: forwardRef(() => CaoDropDownComponent),
47
+ multi: true,
48
+ },
49
+ ],
50
+ })
51
+ export class CaoDropDownComponent implements ControlValueAccessor, OnInit, OnDestroy {
52
+ @Input() textHeader = "SELECIONE UM ITEM";
53
+ @Input() options: ICaoDropDownOption[] = [];
54
+ @Input() dropDownClass = "";
55
+ @Input() styleDropDown: { [klass: string]: unknown } = {};
56
+ @Input() control?: AbstractControl | null;
57
+ @Input() controlName = "";
58
+ @Input() isDisabled = false;
59
+ @Input() tooltip = "";
60
+ @Input() dropDirectionInput: "up" | "down" = "down";
61
+ @Input() dataCy?: string = "";
62
+
63
+ @Input() trackByFn?: (index: number, item: ICaoDropDownOption) => unknown;
64
+ @Input() compareWith?: (option: unknown, value: unknown) => boolean;
65
+ @Input() ariaLabel?: string;
66
+ @Input() ariaLabelledBy?: string;
67
+ @Input() listAriaLabel?: string;
68
+
69
+ @Output() readonly itemSelected = new EventEmitter<ICaoDropDownOption>();
70
+
71
+ isOpen = false;
72
+ selectedItem?: ICaoDropDownOption;
73
+ filteredOptions: ICaoDropDownOption[] = [];
74
+ searchControl = new FormControl("");
75
+ isTouched = false;
76
+ isFocused = false;
77
+ dropDirection: string | undefined;
78
+ removeValidBorder = false;
79
+
80
+
81
+ focusedIndex = -1;
82
+
83
+ private _handleClickOutside = this.onClickOutside.bind(this);
84
+
85
+ constructor(
86
+ private _eref: ElementRef,
87
+ private cdr: ChangeDetectorRef
88
+ ) { }
89
+
90
+ ngOnInit(): void {
91
+ document.addEventListener("click", this._handleClickOutside, true);
92
+ this.filteredOptions = [...this.options];
93
+
94
+ this.searchControl.valueChanges.subscribe((value) => {
95
+ this.filterOptions(value || "");
96
+ this.focusedIndex = -1
97
+ });
98
+ }
99
+
100
+ ngOnDestroy(): void {
101
+ document.removeEventListener("click", this._handleClickOutside, true);
102
+ }
103
+
104
+ get activeDescendant(): string | null {
105
+ if (this.isOpen && this.focusedIndex >= 0 && this.filteredOptions[this.focusedIndex]) {
106
+ return `option-${this.dataCy}-${this.focusedIndex}`;
107
+ }
108
+ return null;
109
+ }
110
+
111
+ toggleDropdown(): void {
112
+ if (this.isDisabled) { return };
113
+
114
+ this.checkAvailableSpace();
115
+ this.isOpen = !this.isOpen;
116
+ this.isFocused = this.isOpen;
117
+ this.markAsTouched();
118
+
119
+ if (this.isOpen) {
120
+ this.searchControl.setValue("");
121
+ this.filteredOptions = [...this.options];
122
+ this.removeValidBorder = false;
123
+ this.focusedIndex = -1;
124
+
125
+
126
+ setTimeout(() => {
127
+ if (this.isOpen) {
128
+ const searchInput = this._eref.nativeElement.querySelector('.dropdown-search input');
129
+ if (searchInput) {
130
+ searchInput.focus();
131
+ }
132
+ }
133
+ });
134
+ } else {
135
+ this.focusedIndex = -1;
136
+ }
137
+
138
+ this.cdr.detectChanges();
139
+ }
140
+
141
+ selectItem(option: ICaoDropDownOption): void {
142
+ if (this.isDisabled) { return };
143
+
144
+ this.selectedItem = option;
145
+ this.isOpen = false;
146
+ this.focusedIndex = -1;
147
+ this.itemSelected.emit(option);
148
+ this.onChange(option);
149
+ this.markAsTouched();
150
+ this.cdr.detectChanges();
151
+ }
152
+
153
+ clear(): void {
154
+ this.selectedItem = undefined;
155
+ this.onChange(null);
156
+ this.cdr.detectChanges();
157
+ }
158
+
159
+ filterOptions(searchTerm: string) {
160
+ if (!searchTerm) {
161
+ this.filteredOptions = [...this.options];
162
+ return;
163
+ }
164
+ const lowerCaseSearchTerm = searchTerm.toLowerCase();
165
+ this.filteredOptions = this.options.filter(
166
+ (option) =>
167
+ option.nome.toLowerCase().includes(lowerCaseSearchTerm) ||
168
+ option.id?.toString().toLowerCase().includes(lowerCaseSearchTerm)
169
+ );
170
+ }
171
+
172
+ trackBy(index: number, item: ICaoDropDownOption): unknown {
173
+ return this.trackByFn ? this.trackByFn(index, item) : item.id;
174
+ }
175
+
176
+ private onClickOutside(event: MouseEvent): void {
177
+ if (!this._eref.nativeElement.contains(event.target)) {
178
+ if (this.isOpen) {
179
+ this.isOpen = false;
180
+ }
181
+ if (this.isFocused) {
182
+ this.isFocused = false;
183
+ }
184
+ this.focusedIndex = -1;
185
+ this.markAsTouched();
186
+ this.removeValidBorder = true;
187
+ this.cdr.detectChanges();
188
+ }
189
+ }
190
+
191
+ onArrowDown(event: Event): void {
192
+ if (this.isDisabled) { return };
193
+
194
+ event.preventDefault();
195
+ event.stopPropagation();
196
+
197
+ if (this.filteredOptions.length === 0) { return };
198
+
199
+ if (this.focusedIndex === -1) {
200
+
201
+ this.focusedIndex = 0;
202
+ } else {
203
+
204
+ this.focusedIndex = (this.focusedIndex + 1) % this.filteredOptions.length;
205
+ }
206
+
207
+ this.scrollIntoView();
208
+ this.cdr.detectChanges();
209
+ }
210
+
211
+ onArrowUp(event: Event): void {
212
+ if (this.isDisabled) { return };
213
+
214
+ event.preventDefault();
215
+ event.stopPropagation();
216
+
217
+ if (this.filteredOptions.length === 0) { return };
218
+
219
+ if (this.focusedIndex === -1) {
220
+
221
+ this.focusedIndex = this.filteredOptions.length - 1;
222
+ } else {
223
+
224
+ this.focusedIndex = this.focusedIndex <= 0
225
+ ? this.filteredOptions.length - 1
226
+ : this.focusedIndex - 1;
227
+ }
228
+
229
+ this.scrollIntoView();
230
+ this.cdr.detectChanges();
231
+ }
232
+
233
+ onSearchEnter(event: Event): void {
234
+ event.preventDefault();
235
+ event.stopPropagation();
236
+
237
+ if (this.focusedIndex >= 0 && this.focusedIndex < this.filteredOptions.length) {
238
+ this.selectItem(this.filteredOptions[this.focusedIndex]);
239
+ } else if (this.filteredOptions.length > 0) {
240
+
241
+ this.selectItem(this.filteredOptions[0]);
242
+ }
243
+ }
244
+
245
+ closeDropdown(): void {
246
+ if (this.isDisabled) { return };
247
+
248
+ this.isOpen = false;
249
+ this.focusedIndex = -1;
250
+ this.isFocused = false;
251
+ this.cdr.detectChanges();
252
+ }
253
+
254
+ private scrollIntoView(): void {
255
+ if (!this.isOpen || this.focusedIndex === -1) { return };
256
+
257
+ setTimeout(() => {
258
+ const element = document.getElementById(`option-${this.dataCy}-${this.focusedIndex}`);
259
+ if (element) {
260
+ element.scrollIntoView({
261
+ block: 'nearest',
262
+ behavior: 'smooth'
263
+ });
264
+ }
265
+ }, 0);
266
+ }
267
+
268
+ checkAvailableSpace(): void {
269
+ if (this.dropDirectionInput === "up") {
270
+ this.dropDirection = "up";
271
+ return;
272
+ }
273
+
274
+ const rect = this._eref.nativeElement.getBoundingClientRect();
275
+ const windowHeight = window.innerHeight;
276
+ const dropdownHeight = 200
277
+
278
+ const spaceBelow = windowHeight - rect.bottom;
279
+ this.dropDirection = spaceBelow < dropdownHeight ? "up" : "down";
280
+ }
281
+
282
+
283
+ writeValue(value: unknown): void {
284
+ if (value !== null && value !== undefined) {
285
+ const match = this.options.find((opt) =>
286
+ this.compareWith
287
+ ? this.compareWith(opt, value)
288
+ : opt.id === (typeof value === "object" && value ? (value as any).id : value)
289
+ );
290
+
291
+ if (match) {
292
+ this.selectedItem = match;
293
+ } else {
294
+ const matchByName = this.options.find((opt) => opt.nome === value);
295
+ if (matchByName) {
296
+ this.selectedItem = matchByName;
297
+ }
298
+ }
299
+ } else {
300
+ this.selectedItem = undefined;
301
+ }
302
+ this.cdr.detectChanges();
303
+ }
304
+
305
+ registerOnChange(fn: (value: unknown) => void): void {
306
+ this.onChange = fn;
307
+ }
308
+
309
+ registerOnTouched(fn: () => void): void {
310
+ this.onTouched = fn;
311
+ }
312
+
313
+ onChange: (value: unknown) => void = () => { };
314
+ onTouched = () => { };
315
+
316
+ get errorText(): string | null {
317
+ if (!this.control) { return null; }
318
+ if (this.control.hasError("required")) {
319
+ return "*Campo obrigatório";
320
+ }
321
+ return null;
322
+ }
323
+
324
+ private markAsTouched() {
325
+ if (!this.isTouched) {
326
+ this.onTouched();
327
+ this.isTouched = true;
328
+ this.control?.markAsTouched();
329
+ }
330
+ }
331
+
332
+ get controlNameValue(): string {
333
+ return this.controlName;
334
+ }
335
+ }
@@ -0,0 +1,29 @@
1
+ * {
2
+ list-style: none;
3
+ text-decoration: none;
4
+ }
5
+
6
+ .footer-component{
7
+ display: flex;
8
+ align-items: end;
9
+ justify-content: end;
10
+ height: 35px;
11
+ padding: 0 50px 10px 0;
12
+ position: fixed;
13
+ bottom: 0;
14
+ right: 0;
15
+ width: 100%;
16
+ z-index: 100;
17
+ }
18
+
19
+ .footer-component .flex{
20
+ display: flex;
21
+
22
+ }
23
+ .footer-component.isFixed{
24
+ position: fixed;
25
+ }
26
+
27
+ .footer-component.col{
28
+ flex-direction: column;
29
+ }
@@ -0,0 +1,10 @@
1
+ <footer class="footer-element" [class.fixed]="isFixed">
2
+ <div class="flex col">
3
+ <ng-content />
4
+ @if(footerText){
5
+ <ul class="footer-end">
6
+ <li>{{ footerText }} © {{ currentYear }}</li>
7
+ </ul>
8
+ }
9
+ </div>
10
+ </footer>
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
2
+
3
+ import { Footer } from "./footer";
4
+
5
+ describe("Footer", () => {
6
+ let component: Footer;
7
+ let fixture: ComponentFixture<Footer>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [Footer],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(Footer);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it("should create", () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,51 @@
1
+ import type { Meta, StoryObj } from '@storybook/angular-vite';
2
+
3
+ import { Footer } from './footer';
4
+
5
+ const meta: Meta<Footer> = {
6
+ title: 'Componentes/Footer',
7
+ component: Footer,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ currentRoute: { control: 'text' },
11
+ footerText: { control: 'text' },
12
+ isFixed: { control: 'boolean' },
13
+ },
14
+ args: {
15
+ footerText: 'Opalbytes',
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+ type Story = StoryObj<Footer>;
21
+
22
+ export const Padrao: Story = {
23
+ args: {
24
+ footerText: 'Opalbytes',
25
+ },
26
+ };
27
+
28
+ export const Fixo: Story = {
29
+ args: {
30
+ footerText: 'Opalbytes',
31
+ isFixed: true,
32
+ },
33
+ };
34
+
35
+ export const ComRota: Story = {
36
+ args: {
37
+ footerText: 'Opalbytes',
38
+ currentRoute: '/dashboard',
39
+ },
40
+ };
41
+
42
+ export const ComConteudo: Story = {
43
+ render: (args) => ({
44
+ template: `
45
+ <cao-footer [currentRoute]="currentRoute" [footerText]="footerText" [isFixed]="isFixed">
46
+ <div>Links úteis: <a href="#">Termos</a> | <a href="#">Privacidade</a></div>
47
+ </cao-footer>
48
+ `,
49
+ props: args,
50
+ }),
51
+ };
@@ -0,0 +1,15 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import { Component, Input } from "@angular/core";
3
+
4
+ @Component({
5
+ selector: "cao-footer",
6
+ templateUrl: "./footer.html",
7
+ styleUrl: "./footer.css",
8
+ imports: [ CommonModule ]
9
+ })
10
+ export class Footer {
11
+ currentYear: number = new Date().getFullYear();
12
+ @Input() currentRoute?:string;
13
+ @Input() footerText?:string;
14
+ @Input() isFixed = true;
15
+ }
@@ -0,0 +1,19 @@
1
+ export * from './base-button/base-button.component'
2
+ export * from './base-input/base-input'
3
+ export * from './base-time-range/base-time-range'
4
+
5
+ export * from './autocomplete/autocomplete.component'
6
+ export * from './drop-down/drop-down'
7
+ export * from './links-button/links-button'
8
+ export * from './footer/footer'
9
+ export * from './paginator/paginator'
10
+ export * from './time-picker/time-picker'
11
+
12
+ export * from './stepper/stepper'
13
+ export * from './stepper/step/step'
14
+
15
+ export * from "./tab-group/tab-group"
16
+ export * from "./tab-panel/tab-panel"
17
+
18
+ export * from "./base-icon/base-icon"
19
+ export * from "./kpi/kpi"
File without changes
@@ -0,0 +1,11 @@
1
+ <div
2
+ class="flex flex-col py-2 pt-2.25 px-3.75 rounded-card border-b w-46.25 bg-(--color-white)"
3
+ [style.border-color]="kpiData().borderColor">
4
+ <div class="flex w-full justify-end">
5
+ <img class="w-6 h-6 mr-2" [src]="kpiData().icon.src" [alt]="kpiData().icon.alt" />
6
+ </div>
7
+ <div class="flex flex-col pl-1.5">
8
+ <p class="text-2xl" [style.color]="kpiData().textColor">{{ kpiData().value }}</p>
9
+ <h3 class="text-base" [style.color]="kpiData().textColor">{{ kpiData().title }}</h3>
10
+ </div>
11
+ </div>
@@ -0,0 +1,38 @@
1
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
2
+
3
+ import { CaoKpi, ICaoKPI } from "./kpi";
4
+
5
+ const mockKpiData: ICaoKPI = {
6
+ title: "Test",
7
+ value: "42",
8
+ textColor: "#000000",
9
+ borderColor: "#ffffff",
10
+ icon: { src: "icon.png", alt: "icon" },
11
+ };
12
+
13
+ describe("CaoKpi", () => {
14
+ let component: CaoKpi;
15
+ let fixture: ComponentFixture<CaoKpi>;
16
+
17
+ beforeEach(async () => {
18
+ await TestBed.configureTestingModule({
19
+ imports: [CaoKpi],
20
+ }).compileComponents();
21
+
22
+ fixture = TestBed.createComponent(CaoKpi);
23
+ component = fixture.componentInstance;
24
+ fixture.componentRef.setInput("kpiData", mockKpiData);
25
+ fixture.detectChanges();
26
+ await fixture.whenStable();
27
+ });
28
+
29
+ it("should create", () => {
30
+ expect(component).toBeTruthy();
31
+ });
32
+
33
+ it("should render the kpi data", () => {
34
+ const el: HTMLElement = fixture.nativeElement;
35
+ expect(el.textContent).toContain("42");
36
+ expect(el.textContent).toContain("Test");
37
+ });
38
+ });
@@ -0,0 +1,65 @@
1
+ import type { Meta, StoryObj } from '@storybook/angular-vite';
2
+
3
+ import { CaoKpi, ICaoKPI } from './kpi';
4
+
5
+ const defaultKpi: ICaoKPI = {
6
+ title: 'Faturamento',
7
+ value: 'R$ 128.450,00',
8
+ textColor: '#111827',
9
+ borderColor: '#e5e7eb',
10
+ icon: {
11
+ src: 'https://lucide.dev/icons/trending-up.svg',
12
+ alt: 'ícone de tendência',
13
+ },
14
+ };
15
+
16
+ const meta: Meta<CaoKpi> = {
17
+ title: 'Componentes/KPI',
18
+ component: CaoKpi,
19
+ tags: ['autodocs'],
20
+ argTypes: {
21
+ kpiData: { control: 'object' },
22
+ },
23
+ args: {
24
+ kpiData: defaultKpi,
25
+ },
26
+ };
27
+
28
+ export default meta;
29
+ type Story = StoryObj<CaoKpi>;
30
+
31
+ export const Padrao: Story = {
32
+ args: {
33
+ kpiData: defaultKpi,
34
+ },
35
+ };
36
+
37
+ export const Sucesso: Story = {
38
+ args: {
39
+ kpiData: {
40
+ title: 'Meta de vendas',
41
+ value: '87%',
42
+ textColor: '#16a34a',
43
+ borderColor: '#16a34a',
44
+ icon: {
45
+ src: 'https://lucide.dev/icons/check-circle.svg',
46
+ alt: 'ícone de sucesso',
47
+ },
48
+ },
49
+ },
50
+ };
51
+
52
+ export const Alerta: Story = {
53
+ args: {
54
+ kpiData: {
55
+ title: 'Pedidos pendentes',
56
+ value: '32',
57
+ textColor: '#dc2626',
58
+ borderColor: '#dc2626',
59
+ icon: {
60
+ src: 'https://lucide.dev/icons/alert-triangle.svg',
61
+ alt: 'ícone de alerta',
62
+ },
63
+ },
64
+ },
65
+ };
@@ -0,0 +1,25 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import { Component, input } from "@angular/core";
3
+
4
+ export interface ICaoImage {
5
+ src: string;
6
+ alt: string;
7
+ }
8
+ export interface ICaoKPI {
9
+ title: string;
10
+ value: string;
11
+ textColor: string;
12
+ borderColor: string;
13
+ icon: ICaoImage;
14
+ }
15
+
16
+ @Component({
17
+ selector: "cao-kpi",
18
+ imports: [CommonModule],
19
+ templateUrl: "./kpi.html",
20
+ styleUrl: "./kpi.css",
21
+ standalone: true,
22
+ })
23
+ export class CaoKpi {
24
+ readonly kpiData = input.required<ICaoKPI>();
25
+ }
@@ -0,0 +1,70 @@
1
+ * {
2
+ text-decoration: none;
3
+ }
4
+
5
+ .links-info {
6
+ display: flex;
7
+ flex-direction: column;
8
+ width: 280px;
9
+ }
10
+
11
+ .links-info h2 {
12
+ font-size: 24px;
13
+ font-weight: 700;
14
+ text-align: center;
15
+ text-transform: uppercase;
16
+ color: var(--title-info);
17
+ margin-bottom: 12%;
18
+ text-wrap: wrap;
19
+ height: 64px;
20
+ }
21
+
22
+ .links-info .links {
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ gap: 46px;
27
+ }
28
+
29
+ .links-info .links .link {
30
+ width: 250px;
31
+ height: 100px;
32
+ text-transform: uppercase;
33
+ background-color: var(--primary);
34
+ color: var(--white);
35
+ font-weight: 700;
36
+ padding: 30px;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ text-align: center;
41
+ font-size: 16px;
42
+ border-radius: 20px;
43
+ box-shadow: 0 4px 4px 0 var(--shadow-black);
44
+ cursor: pointer;
45
+ }
46
+
47
+ .links-info .links .link.disabled {
48
+ background-color: var(--input-disabled-bg);
49
+ color: var(--black);
50
+ pointer-events: none;
51
+ cursor: default;
52
+ }
53
+
54
+ .links-info .links .link:active {
55
+ background-color: var(--active-success-btn);
56
+ }
57
+
58
+ .links-info .links .link:hover {
59
+ background: linear-gradient(125.78deg, var(--primary) 7.03%, var(--confirm-color) 96.73%);
60
+ }
61
+
62
+ @media (max-width: 768px) {
63
+ .links-info h2 {
64
+ height: 0;
65
+ }
66
+
67
+ .links-info .links {
68
+ padding-bottom: 20px;
69
+ }
70
+ }