@foxeltech/angular-ui 0.2.0 → 0.3.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/components.css +2 -17
- package/fesm2022/foxeltech-angular-ui.mjs +343 -77
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/index.d.ts +123 -34
- package/package.json +4 -5
- package/src/lib/styles/components.css +2 -17
- package/src/lib/styles/tailwind.css +11 -8
- package/src/lib/styles/tokens.css +131 -0
- package/src/lib/styles/utilities.css +24 -0
- package/src/lib/ui/components/button/button.component.html +4 -1
- package/src/lib/ui/components/checkbox/checkbox.component.html +3 -2
- package/src/lib/ui/components/datetime-picker/datetime-picker.component.html +3 -2
- package/src/lib/ui/components/datetime-picker/datetime-picker.component.scss +3 -0
- package/src/lib/ui/components/drawer/drawer.component.html +3 -0
- package/src/lib/ui/components/drawer/drawer.component.scss +3 -0
- package/src/lib/ui/components/input/input.component.html +15 -3
- package/src/lib/ui/components/kanban-board/kanban-board.component.scss +3 -0
- package/src/lib/ui/components/master-detail/master-detail.component.css +11 -0
- package/src/lib/ui/components/master-detail/master-detail.component.html +70 -0
- package/src/lib/ui/components/meter/meter.component.html +8 -1
- package/src/lib/ui/components/radio-button/radio-button.component.html +10 -4
- package/src/lib/ui/components/radio-button-toggle/radio-button-toggle.component.html +8 -3
- package/src/lib/ui/components/rich-text-area/rich-text-area.component.scss +3 -0
- package/src/lib/ui/components/select/select.component.html +9 -4
- package/src/lib/ui/components/severity-badges/severity-badges.component.html +5 -4
- package/src/lib/ui/components/skeleton-list/skeleton-list.component.html +2 -2
- package/src/lib/ui/components/slider/slider.component.html +3 -0
- package/src/lib/ui/components/switch/switch.component.html +11 -6
- package/src/lib/ui/components/tab-group/tab-group.component.html +15 -1
- package/src/lib/ui/components/tab-group/tab-group.component.scss +3 -0
- package/src/lib/ui/components/tag/tag.component.html +6 -1
- package/src/lib/ui/components/toast/toast.component.html +2 -1
- package/src/lib/ui/dialogs/confirmation/confirmation.component.html +1 -1
- package/tailwind.css +11 -8
- package/tokens.css +131 -0
- package/utilities.css +24 -0
- package/tailwind-config.d.ts +0 -4
- package/tailwind.config.js +0 -113
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
<div class="flex flex-col mb-4 w-full text-left">
|
|
2
2
|
@if (label) {
|
|
3
|
-
<label class="txt-field-label">
|
|
3
|
+
<label class="txt-field-label" [attr.for]="fieldId">
|
|
4
4
|
{{ label }}
|
|
5
|
-
<span *ngIf="required" class="txt-required">*</span>
|
|
5
|
+
<span *ngIf="required" class="txt-required" aria-hidden="true">*</span>
|
|
6
6
|
</label>
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
<div class="relative">
|
|
10
10
|
<input
|
|
11
|
+
[id]="fieldId"
|
|
11
12
|
[owlDateTimeTrigger]="picker"
|
|
12
13
|
[owlDateTime]="picker"
|
|
13
14
|
[placeholder]="placeholder"
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/* Tailwind v4: component styles compile in isolation — @reference pulls in the theme for @apply without re-emitting it. */
|
|
2
|
+
@reference '../../../styles/tailwind.css';
|
|
3
|
+
|
|
1
4
|
// ─── Owl DateTime Picker Custom Styles ───────────────────────────────────────
|
|
2
5
|
|
|
3
6
|
// Container
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
@if (logo) {
|
|
8
8
|
<img
|
|
9
9
|
[src]="logo"
|
|
10
|
+
alt="Home"
|
|
10
11
|
class="h-[20px] w-auto transition-all duration-300 ease-in-out"
|
|
11
12
|
[ngClass]="{ 'scale-90': !isExpanded, 'ml-5': isExpanded }"
|
|
12
13
|
/>
|
|
@@ -35,6 +36,8 @@
|
|
|
35
36
|
<button
|
|
36
37
|
type="button"
|
|
37
38
|
class="toggle-btn"
|
|
39
|
+
[attr.aria-label]="isExpanded ? 'Collapse navigation' : 'Expand navigation'"
|
|
40
|
+
[attr.aria-expanded]="isExpanded"
|
|
38
41
|
(click)="toggleDrawer()"
|
|
39
42
|
[matTooltip]="isExpanded ? 'Collapse' : 'Expand'"
|
|
40
43
|
matTooltipClass="text-text-primary text-xs font-semibold"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<div class="mb-4 text-left w-full text-text-primary" [class]="class">
|
|
2
2
|
@if (label) {
|
|
3
|
-
<label class="block txt-field-label">
|
|
3
|
+
<label class="block txt-field-label" [attr.for]="fieldId">
|
|
4
4
|
{{ label }}
|
|
5
5
|
@if (required) {
|
|
6
|
-
<span class="txt-required">*</span>
|
|
6
|
+
<span class="txt-required" aria-hidden="true">*</span>
|
|
7
7
|
}
|
|
8
8
|
@if (tooltip) {
|
|
9
9
|
<fx-ui-hero-icon
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
<input
|
|
23
23
|
appTrimOnBlur
|
|
24
24
|
[appTrimOnBlurCollapse]="true"
|
|
25
|
+
[id]="fieldId"
|
|
25
26
|
[type]="displayType"
|
|
26
27
|
[placeholder]="placeholder"
|
|
27
28
|
[disabled]="disabled"
|
|
@@ -30,6 +31,9 @@
|
|
|
30
31
|
(blur)="onBlur()"
|
|
31
32
|
(focus)="onFocus()"
|
|
32
33
|
[attr.maxlength]="maxlength"
|
|
34
|
+
[attr.aria-invalid]="invalid || null"
|
|
35
|
+
[attr.aria-describedby]="invalid ? fieldId + '-error' : null"
|
|
36
|
+
[attr.aria-required]="required ? true : null"
|
|
33
37
|
[class]="type === 'otp' ? 'input-otp' : 'input-default'"
|
|
34
38
|
[class.input-invalid]="invalid"
|
|
35
39
|
[class.pr-8]="type === 'password' || suffixIcon"
|
|
@@ -41,18 +45,26 @@
|
|
|
41
45
|
class="absolute inset-y-0 right-2 flex items-center text-text-primary hover:text-text-secondary"
|
|
42
46
|
(click)="onSuffixClick()"
|
|
43
47
|
[disabled]="disabled"
|
|
48
|
+
[attr.aria-label]="
|
|
49
|
+
type === 'password'
|
|
50
|
+
? (showPassword ? 'Hide password' : 'Show password')
|
|
51
|
+
: (suffixAriaLabel || null)
|
|
52
|
+
"
|
|
53
|
+
[attr.aria-pressed]="type === 'password' ? showPassword : null"
|
|
44
54
|
>
|
|
45
55
|
@if (type === 'password') {
|
|
46
56
|
<fx-ui-hero-icon
|
|
47
57
|
[icon]="showPassword ? 'eye-slash' : 'eye'"
|
|
48
58
|
[size]="18"
|
|
49
59
|
class="text-text-primary"
|
|
60
|
+
aria-hidden="true"
|
|
50
61
|
></fx-ui-hero-icon>
|
|
51
62
|
} @else if (suffixIcon) {
|
|
52
63
|
<fx-ui-hero-icon
|
|
53
64
|
[icon]="suffixIcon"
|
|
54
65
|
[size]="18"
|
|
55
66
|
class="text-text-primary"
|
|
67
|
+
aria-hidden="true"
|
|
56
68
|
></fx-ui-hero-icon>
|
|
57
69
|
}
|
|
58
70
|
</button>
|
|
@@ -60,7 +72,7 @@
|
|
|
60
72
|
</div>
|
|
61
73
|
|
|
62
74
|
@if (invalid) {
|
|
63
|
-
<div class="txt-invalid">
|
|
75
|
+
<div class="txt-invalid" role="alert" [id]="fieldId + '-error'">
|
|
64
76
|
{{ getErrorMessage(label) }}
|
|
65
77
|
</div>
|
|
66
78
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div class="flex flex-col lg:flex-row items-start gap-xl" [style.--fx-md-list-width]="listWidth">
|
|
2
|
+
<!-- Master: list pane -->
|
|
3
|
+
<div class="fx-md-list card-common">
|
|
4
|
+
<ng-content select="[mdHeader]"></ng-content>
|
|
5
|
+
<ng-content select="[mdFilters]"></ng-content>
|
|
6
|
+
<div
|
|
7
|
+
class="flex flex-col gap-normal overflow-auto pr-xs"
|
|
8
|
+
[style.maxHeight]="maxListHeight"
|
|
9
|
+
role="listbox"
|
|
10
|
+
[attr.aria-label]="listLabel || null"
|
|
11
|
+
>
|
|
12
|
+
@if (table.loading) {
|
|
13
|
+
<fx-ui-skeleton-list [rows]="table.pageSize"></fx-ui-skeleton-list>
|
|
14
|
+
} @else {
|
|
15
|
+
@for (row of table.dataSource.data; track table.idOf(row); let i = $index) {
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
role="option"
|
|
19
|
+
[id]="optionId(i)"
|
|
20
|
+
[attr.aria-selected]="isSelected(row)"
|
|
21
|
+
[tabindex]="isSelected(row) ? 0 : -1"
|
|
22
|
+
(click)="table.selectItem(row)"
|
|
23
|
+
(keydown)="onKeydown($event, i)"
|
|
24
|
+
class="text-left rounded-lg border p-semi transition-colors"
|
|
25
|
+
[ngClass]="
|
|
26
|
+
isSelected(row)
|
|
27
|
+
? 'border-gradient-primary-start bg-gradient-primary-start/5'
|
|
28
|
+
: 'border-border-default hover:border-border-interactive bg-bg-primary'
|
|
29
|
+
"
|
|
30
|
+
>
|
|
31
|
+
<ng-container
|
|
32
|
+
[ngTemplateOutlet]="rowTemplate"
|
|
33
|
+
[ngTemplateOutletContext]="{ $implicit: row, selected: isSelected(row) }"
|
|
34
|
+
></ng-container>
|
|
35
|
+
</button>
|
|
36
|
+
}
|
|
37
|
+
@if (!table.dataSource.data.length) {
|
|
38
|
+
<fx-ui-empty-state [message]="emptyMessage"></fx-ui-empty-state>
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
</div>
|
|
42
|
+
<!-- No (page) binding: ngAfterViewInit hands this paginator to the table,
|
|
43
|
+
whose own BaseTable subscription (running after this child's hook)
|
|
44
|
+
wires page events — a binding here would double-fetch every change. -->
|
|
45
|
+
<mat-paginator
|
|
46
|
+
class="mt-normal"
|
|
47
|
+
[length]="table.total"
|
|
48
|
+
[pageSize]="table.pageSize"
|
|
49
|
+
[pageSizeOptions]="pageSizeOptions"
|
|
50
|
+
></mat-paginator>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Detail pane -->
|
|
54
|
+
<div class="w-full lg:flex-1 min-w-0">
|
|
55
|
+
@if (table.selected) {
|
|
56
|
+
@if (table.detailBusy) {
|
|
57
|
+
<fx-ui-skeleton-detail [cards]="skeletonCards"></fx-ui-skeleton-detail>
|
|
58
|
+
} @else {
|
|
59
|
+
<ng-container
|
|
60
|
+
[ngTemplateOutlet]="detailTemplate"
|
|
61
|
+
[ngTemplateOutletContext]="{ $implicit: table.detail, selected: table.selected }"
|
|
62
|
+
></ng-container>
|
|
63
|
+
}
|
|
64
|
+
} @else {
|
|
65
|
+
<div class="card-common flex items-center justify-center h-[300px]">
|
|
66
|
+
<fx-ui-empty-state [message]="emptyMessage"></fx-ui-empty-state>
|
|
67
|
+
</div>
|
|
68
|
+
}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
}
|
|
10
10
|
</div>
|
|
11
11
|
}
|
|
12
|
-
<div
|
|
12
|
+
<div
|
|
13
|
+
class="h-2 rounded-full bg-bg-hover overflow-hidden"
|
|
14
|
+
role="progressbar"
|
|
15
|
+
[attr.aria-valuemin]="0"
|
|
16
|
+
[attr.aria-valuemax]="max"
|
|
17
|
+
[attr.aria-valuenow]="value"
|
|
18
|
+
[attr.aria-label]="label || null"
|
|
19
|
+
>
|
|
13
20
|
<div class="h-full rounded-full" [ngClass]="fillClass" [style.width.%]="percent"></div>
|
|
14
21
|
</div>
|
|
15
22
|
</div>
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
<div
|
|
1
|
+
<div
|
|
2
|
+
class="w-full rounded-lg overflow-hidden"
|
|
3
|
+
[class]="class"
|
|
4
|
+
role="radiogroup"
|
|
5
|
+
[attr.aria-labelledby]="label ? groupId + '-label' : null"
|
|
6
|
+
>
|
|
7
|
+
<!-- A radiogroup has no single control a <label> could point at — name it via aria-labelledby. -->
|
|
2
8
|
@if (label) {
|
|
3
|
-
<
|
|
9
|
+
<span class="txt-field-label block" [id]="groupId + '-label'">
|
|
4
10
|
{{ label }}
|
|
5
11
|
@if (required) {
|
|
6
|
-
<span class="txt-required">*</span>
|
|
12
|
+
<span class="txt-required" aria-hidden="true">*</span>
|
|
7
13
|
}
|
|
8
14
|
@if (tooltip) {
|
|
9
15
|
<fx-ui-hero-icon
|
|
@@ -15,7 +21,7 @@
|
|
|
15
21
|
matTooltipClass="text-text-primary text-xs font-semibold"
|
|
16
22
|
></fx-ui-hero-icon>
|
|
17
23
|
}
|
|
18
|
-
</
|
|
24
|
+
</span>
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
<button
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
<div class="flex flex-col text-left mb-4 w-full" [class]="class">
|
|
2
|
-
|
|
2
|
+
<!-- A radiogroup has no single control a <label> could point at — name it via aria-labelledby. -->
|
|
3
|
+
@if(label){<span class="txt-field-label" [id]="groupId + '-label'">
|
|
3
4
|
{{ label }}
|
|
4
|
-
@if(required) {<span class="txt-required">*</span>} </
|
|
5
|
+
@if(required) {<span class="txt-required" aria-hidden="true">*</span>} </span
|
|
5
6
|
>}
|
|
6
7
|
|
|
7
|
-
<div
|
|
8
|
+
<div
|
|
9
|
+
class="inline-flex w-full rounded-lg overflow-hidden"
|
|
10
|
+
role="radiogroup"
|
|
11
|
+
[attr.aria-labelledby]="label ? groupId + '-label' : null"
|
|
12
|
+
>
|
|
8
13
|
@for(opt of options; track opt){<button
|
|
9
14
|
type="button"
|
|
10
15
|
role="radio"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<div class="mb-4 text-left w-full" [class]="class">
|
|
2
2
|
@if (label) {
|
|
3
|
-
<label class="block txt-field-label">
|
|
3
|
+
<label class="block txt-field-label" [attr.for]="fieldId">
|
|
4
4
|
{{ label }}
|
|
5
5
|
@if (required) {
|
|
6
|
-
<span class="txt-required">*</span>
|
|
6
|
+
<span class="txt-required" aria-hidden="true">*</span>
|
|
7
7
|
}
|
|
8
8
|
@if (tooltip) {
|
|
9
9
|
<fx-ui-hero-icon
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
appearance="fill"
|
|
25
25
|
>
|
|
26
26
|
<mat-select
|
|
27
|
+
[id]="fieldId"
|
|
28
|
+
[required]="!!required"
|
|
29
|
+
[attr.aria-invalid]="invalid || null"
|
|
30
|
+
[attr.aria-describedby]="invalid ? fieldId + '-error' : null"
|
|
27
31
|
[placeholder]="placeholder"
|
|
28
32
|
[disabled]="disabled"
|
|
29
33
|
[multiple]="multiple"
|
|
@@ -36,11 +40,12 @@
|
|
|
36
40
|
<div class="px-semi w-full">
|
|
37
41
|
<input
|
|
38
42
|
type="text"
|
|
43
|
+
aria-label="Search"
|
|
39
44
|
[placeholder]="'Search'"
|
|
40
45
|
[(ngModel)]="searchTerm"
|
|
41
46
|
(ngModelChange)="onSearchChange($event)"
|
|
42
47
|
(click)="$event.stopPropagation()"
|
|
43
|
-
class="text-text-primary placeholder:text-text-placeholder w-full bg-bg-primary border-b px-semi py-normal border-border-default focus:outline-none focus:border-gradient-primary-start text-sm text-text-primary tracking-normal ring-0 transition-colors duration-500 ease-in-out"
|
|
48
|
+
class="text-text-primary placeholder:text-text-placeholder w-full bg-bg-primary border-b px-semi py-normal border-border-default focus:outline-none focus:border-gradient-primary-start focus-visible:ring-2 focus-visible:ring-primary/50 text-sm text-text-primary tracking-normal ring-0 transition-colors duration-500 ease-in-out"
|
|
44
49
|
/>
|
|
45
50
|
</div>
|
|
46
51
|
}
|
|
@@ -51,7 +56,7 @@
|
|
|
51
56
|
</mat-form-field>
|
|
52
57
|
|
|
53
58
|
@if (invalid) {
|
|
54
|
-
<div class="txt-invalid text-left">
|
|
59
|
+
<div class="txt-invalid text-left" role="alert" [id]="fieldId + '-error'">
|
|
55
60
|
{{ getErrorMessage(label) }}
|
|
56
61
|
</div>
|
|
57
62
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<div class="flex items-center gap-small">
|
|
2
2
|
@for (badge of badges; track badge.key) {
|
|
3
|
-
<
|
|
4
|
-
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
class="px-2 py-0.5 rounded-full txt-utility-caption font-medium cursor-pointer focus-visible:ring-2 focus-visible:ring-primary/50"
|
|
5
6
|
[ngClass]="badgeClass(badge)"
|
|
7
|
+
[attr.aria-label]="badge.key + ': ' + badge.label"
|
|
6
8
|
(click)="badgeClick.emit(badge.key)"
|
|
7
|
-
|
|
8
|
-
>
|
|
9
|
+
>{{ badge.label }}</button>
|
|
9
10
|
}
|
|
10
11
|
</div>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div class="flex flex-col gap-normal animate-pulse">
|
|
2
2
|
@for (r of range(rows); track r) {
|
|
3
|
-
<div class="rounded-
|
|
3
|
+
<div class="rounded-lg border border-border-default p-semi">
|
|
4
4
|
<div class="flex items-center gap-normal mb-small">
|
|
5
|
-
<div class="w-8 h-8 rounded-
|
|
5
|
+
<div class="w-8 h-8 rounded-lg bg-bg-hover shrink-0"></div>
|
|
6
6
|
<div class="h-4 flex-1 rounded bg-bg-hover"></div>
|
|
7
7
|
<div class="h-5 w-12 rounded-full bg-bg-hover shrink-0"></div>
|
|
8
8
|
</div>
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
[class.opacity-60]="disabled"
|
|
5
5
|
role="group"
|
|
6
6
|
>
|
|
7
|
+
<!-- Track click is a pointer shortcut; the thumb button below owns keyboard/focus. -->
|
|
8
|
+
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
7
9
|
<div
|
|
8
10
|
#track
|
|
9
11
|
class="relative touch-none py-4"
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
class="absolute -mt-1 -ml-1.5 w-4 h-4 rounded-full shadow focus:ring-primary bg-primary border-2 border-text-primary"
|
|
39
41
|
[ngStyle]="thumbStyle()"
|
|
40
42
|
role="slider"
|
|
43
|
+
aria-label="Slider"
|
|
41
44
|
[attr.aria-valuemin]="min"
|
|
42
45
|
[attr.aria-valuemax]="max"
|
|
43
46
|
[attr.aria-valuenow]="value"
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
1
|
+
<button
|
|
2
|
+
type="button"
|
|
3
|
+
role="switch"
|
|
4
|
+
[attr.aria-checked]="!!value"
|
|
5
|
+
[attr.aria-label]="ariaLabel || null"
|
|
6
|
+
[disabled]="disabled"
|
|
7
|
+
class="relative flex items-center w-10 h-5 rounded-full transition-colors duration-300 cursor-pointer disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-primary/50"
|
|
3
8
|
[ngClass]="{
|
|
4
9
|
'bg-success': value && !disabled,
|
|
5
10
|
'bg-button-default': !value && !disabled,
|
|
@@ -8,14 +13,14 @@
|
|
|
8
13
|
(click)="toggle()"
|
|
9
14
|
>
|
|
10
15
|
@if(value) {
|
|
11
|
-
<i style="font-size: 20px;" class="material-symbols-outlined absolute left-0.5 top-0 text-text-inverse">check</i>
|
|
12
|
-
|
|
16
|
+
<i style="font-size: 20px;" class="material-symbols-outlined absolute left-0.5 top-0 text-text-inverse" aria-hidden="true">check</i>
|
|
17
|
+
|
|
13
18
|
}
|
|
14
19
|
@if(!value) {
|
|
15
|
-
<i style="font-size: 20px;" class="material-symbols-outlined absolute right-0.5 top-0 text-text-inverse">close</i>
|
|
20
|
+
<i style="font-size: 20px;" class="material-symbols-outlined absolute right-0.5 top-0 text-text-inverse" aria-hidden="true">close</i>
|
|
16
21
|
}
|
|
17
22
|
<div
|
|
18
23
|
class="absolute top-0.5 left-0.5 w-4 h-4 bg-text-inverse rounded-full shadow-md transform transition-transform duration-300"
|
|
19
24
|
[ngClass]="{ 'translate-x-5': value }"
|
|
20
25
|
></div>
|
|
21
|
-
</
|
|
26
|
+
</button>
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
<button
|
|
4
4
|
class="left-scroll-button"
|
|
5
5
|
type="button"
|
|
6
|
+
aria-label="Scroll tabs left"
|
|
6
7
|
(click)="scroll('left')"
|
|
7
8
|
[style.opacity]="canScrollLeft ? 1 : 0"
|
|
8
9
|
[style.pointerEvents]="canScrollLeft ? 'auto' : 'none'"
|
|
10
|
+
[attr.tabindex]="canScrollLeft ? null : -1"
|
|
11
|
+
[attr.aria-hidden]="!canScrollLeft"
|
|
9
12
|
>
|
|
10
13
|
<svg
|
|
14
|
+
aria-hidden="true"
|
|
11
15
|
xmlns="http://www.w3.org/2000/svg"
|
|
12
16
|
class="h-5 w-5 text-text-primary"
|
|
13
17
|
fill="none"
|
|
@@ -20,12 +24,18 @@
|
|
|
20
24
|
|
|
21
25
|
<div
|
|
22
26
|
#tabList
|
|
27
|
+
role="tablist"
|
|
23
28
|
class="relative flex overflow-x-auto no-scrollbar scroll-smooth"
|
|
24
29
|
(scroll)="onScroll()"
|
|
25
30
|
>
|
|
26
31
|
<button
|
|
27
32
|
*ngFor="let tab of tabs.toArray(); let i = index"
|
|
33
|
+
type="button"
|
|
34
|
+
role="tab"
|
|
35
|
+
[attr.aria-selected]="i === activeIndex"
|
|
36
|
+
[tabindex]="i === activeIndex ? 0 : -1"
|
|
28
37
|
(click)="selectTab(i)"
|
|
38
|
+
(keydown)="onTabKeydown($event, i)"
|
|
29
39
|
class="tab-btn tab-bar-btn"
|
|
30
40
|
>
|
|
31
41
|
<div class="flex flex-row items-center justify-center">
|
|
@@ -56,11 +66,15 @@
|
|
|
56
66
|
<button
|
|
57
67
|
class="right-scroll-button"
|
|
58
68
|
type="button"
|
|
69
|
+
aria-label="Scroll tabs right"
|
|
59
70
|
(click)="scroll('right')"
|
|
60
71
|
[style.opacity]="canScrollRight ? 1 : 0"
|
|
61
72
|
[style.pointerEvents]="canScrollRight ? 'auto' : 'none'"
|
|
73
|
+
[attr.tabindex]="canScrollRight ? null : -1"
|
|
74
|
+
[attr.aria-hidden]="!canScrollRight"
|
|
62
75
|
>
|
|
63
76
|
<svg
|
|
77
|
+
aria-hidden="true"
|
|
64
78
|
xmlns="http://www.w3.org/2000/svg"
|
|
65
79
|
class="h-5 w-5 text-text-primary"
|
|
66
80
|
fill="none"
|
|
@@ -75,7 +89,7 @@
|
|
|
75
89
|
<!-- Tab content -->
|
|
76
90
|
<div class="transition-all duration-300 ease-in-out mt-4">
|
|
77
91
|
<ng-container *ngFor="let tab of tabs.toArray(); let i = index">
|
|
78
|
-
<div *ngIf="i === activeIndex" class="animate-fadeIn">
|
|
92
|
+
<div *ngIf="i === activeIndex" role="tabpanel" class="animate-fadeIn">
|
|
79
93
|
<ng-container *ngTemplateOutlet="tab.content"></ng-container>
|
|
80
94
|
</div>
|
|
81
95
|
</ng-container>
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
<!-- Interactive semantics only when a (clicked) listener is attached. -->
|
|
1
2
|
<div
|
|
2
3
|
(click)="onClick()"
|
|
4
|
+
(keydown.enter)="onClick()"
|
|
5
|
+
(keydown.space)="$event.preventDefault(); onClick()"
|
|
6
|
+
[attr.role]="clicked.observed ? 'button' : null"
|
|
7
|
+
[attr.tabindex]="clicked.observed ? 0 : null"
|
|
3
8
|
[class]="
|
|
4
|
-
`flex flex-row gap-small ${rounded ? 'tag-round' : 'tag-square'} tag-${type}${solid ? '-solid' : ''}`
|
|
9
|
+
`flex flex-row gap-small ${rounded ? 'tag-round' : 'tag-square'} tag-${type}${solid ? '-solid' : ''}${clicked.observed ? ' cursor-pointer focus-visible:ring-2 focus-visible:ring-primary/50' : ''}`
|
|
5
10
|
"
|
|
6
11
|
>
|
|
7
12
|
@if (icon) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<div
|
|
2
2
|
animate.enter="horizontal-fade-in-animation"
|
|
3
3
|
animate.leave="horizontal-fade-out-animation"
|
|
4
|
+
[attr.role]="type === 'error' ? 'alert' : 'status'"
|
|
4
5
|
class="flex border-l-[3px] items-center bg-bg-primary gap-semi min-w-[250px] max-w-[400px] px-large py-semi rounded shadow-lg transition-all duration-300 ease-in-out transform opacity-100 translate-y-0"
|
|
5
6
|
[ngClass]="{
|
|
6
7
|
'border-success': type === 'success',
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
'border-information': type === 'info'
|
|
10
11
|
}"
|
|
11
12
|
>
|
|
12
|
-
<div class="">
|
|
13
|
+
<div class="" aria-hidden="true">
|
|
13
14
|
@switch (type) { @case('success') {
|
|
14
15
|
<fx-ui-hero-icon
|
|
15
16
|
icon="check-circle"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div animate.enter="fade-in-animation" class="dialog-container">
|
|
2
|
-
<h1 class="txt-heading-05 dialog-header">{{ title }}</h1>
|
|
2
|
+
<h1 mat-dialog-title class="txt-heading-05 dialog-header">{{ title }}</h1>
|
|
3
3
|
<mat-dialog-content>
|
|
4
4
|
<div class="txt-default">{{ message }}</div>
|
|
5
5
|
</mat-dialog-content>
|
package/tailwind.css
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
/* fonts.css is NOT imported here: Tailwind v4 inlines imports and rebases
|
|
2
|
+
font url()s against the entry file, which breaks webpack resolution.
|
|
3
|
+
Consumers add '@foxeltech/angular-ui/fonts.css' to their styles array. */
|
|
4
|
+
@import 'tailwindcss';
|
|
5
|
+
@import './tokens.css';
|
|
6
|
+
@import './utilities.css';
|
|
1
7
|
@import './theme.css';
|
|
2
|
-
@import './components.css';
|
|
3
|
-
@import './dialogs.css';
|
|
4
|
-
@import './fonts.css';
|
|
8
|
+
@import './components.css' layer(components);
|
|
9
|
+
@import './dialogs.css' layer(components);
|
|
5
10
|
@import './animations.css';
|
|
6
11
|
@import '@danielmoncada/angular-datetime-picker/assets/style/picker.min.css';
|
|
7
|
-
@tailwind base;
|
|
8
|
-
@tailwind components;
|
|
9
|
-
@tailwind utilities;
|
|
10
12
|
|
|
11
|
-
html,
|
|
13
|
+
html,
|
|
14
|
+
body {
|
|
12
15
|
font-family: 'Roboto';
|
|
13
16
|
margin: 0;
|
|
14
17
|
padding: 0;
|
|
15
18
|
height: 100%;
|
|
16
19
|
width: 100%;
|
|
17
20
|
background: rgb(var(--bg-primary));
|
|
18
|
-
}
|
|
21
|
+
}
|