@ng-cn/core 1.0.16 → 1.0.17
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/package.json +1 -1
- package/src/app/lib/components/ui/accordion/accordion-content.component.ts +11 -14
- package/src/app/lib/components/ui/accordion/accordion-context.ts +1 -0
- package/src/app/lib/components/ui/accordion/accordion-item.component.ts +8 -0
- package/src/app/lib/components/ui/accordion/accordion-trigger.component.ts +5 -1
- package/src/app/lib/components/ui/accordion/accordion.component.ts +24 -6
- package/src/app/lib/components/ui/alert/alert-variants.ts +18 -4
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-action.component.ts +1 -0
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-cancel.component.ts +1 -1
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-content.component.ts +25 -9
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-description.component.ts +1 -0
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-footer.component.ts +1 -0
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-header.component.ts +1 -0
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-title.component.ts +1 -0
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/alert-dialog/alert-dialog.component.ts +4 -0
- package/src/app/lib/components/ui/aspect-ratio/aspect-ratio.component.ts +1 -0
- package/src/app/lib/components/ui/avatar/avatar-context.ts +9 -0
- package/src/app/lib/components/ui/avatar/avatar-fallback.component.ts +6 -9
- package/src/app/lib/components/ui/avatar/avatar-image.component.ts +40 -11
- package/src/app/lib/components/ui/avatar/avatar.component.ts +18 -13
- package/src/app/lib/components/ui/avatar/index.ts +1 -0
- package/src/app/lib/components/ui/avatar/ui-avatar.component.ts +9 -20
- package/src/app/lib/components/ui/badge/badge-variants.ts +1 -1
- package/src/app/lib/components/ui/badge/badge.component.ts +1 -0
- package/src/app/lib/components/ui/breadcrumb/breadcrumb-ellipsis.component.ts +1 -0
- package/src/app/lib/components/ui/breadcrumb/breadcrumb-item.component.ts +3 -7
- package/src/app/lib/components/ui/breadcrumb/breadcrumb-link.component.ts +4 -11
- package/src/app/lib/components/ui/breadcrumb/breadcrumb-list.component.ts +3 -7
- package/src/app/lib/components/ui/breadcrumb/breadcrumb-page.component.ts +1 -0
- package/src/app/lib/components/ui/breadcrumb/breadcrumb-separator.component.ts +20 -24
- package/src/app/lib/components/ui/breadcrumb/breadcrumb.component.ts +1 -0
- package/src/app/lib/components/ui/button/button-variants.ts +3 -3
- package/src/app/lib/components/ui/button/button.component.ts +1 -0
- package/src/app/lib/components/ui/button-group/button-group.component.ts +1 -0
- package/src/app/lib/components/ui/collapsible/collapsible-content.component.ts +1 -0
- package/src/app/lib/components/ui/collapsible/collapsible-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/collapsible/collapsible.component.ts +1 -0
- package/src/app/lib/components/ui/combobox/combobox-content.component.ts +1 -0
- package/src/app/lib/components/ui/combobox/combobox-empty.component.ts +1 -0
- package/src/app/lib/components/ui/combobox/combobox-group.component.ts +1 -0
- package/src/app/lib/components/ui/combobox/combobox-input.component.ts +1 -0
- package/src/app/lib/components/ui/combobox/combobox-item.component.ts +1 -4
- package/src/app/lib/components/ui/combobox/combobox-list.component.ts +1 -1
- package/src/app/lib/components/ui/combobox/combobox-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/combobox/combobox-value.component.ts +1 -0
- package/src/app/lib/components/ui/combobox/combobox.component.ts +1 -1
- package/src/app/lib/components/ui/command/command-dialog.component.ts +1 -0
- package/src/app/lib/components/ui/command/command-empty.component.ts +1 -0
- package/src/app/lib/components/ui/command/command-group.component.ts +1 -0
- package/src/app/lib/components/ui/command/command-input.component.ts +1 -0
- package/src/app/lib/components/ui/command/command-item.component.ts +2 -1
- package/src/app/lib/components/ui/command/command-list.component.ts +1 -0
- package/src/app/lib/components/ui/command/command-separator.component.ts +1 -0
- package/src/app/lib/components/ui/command/command-shortcut.component.ts +1 -0
- package/src/app/lib/components/ui/command/command.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-checkbox-item.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-content.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-item.component.ts +2 -1
- package/src/app/lib/components/ui/context-menu/context-menu-label.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-radio-group.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-radio-item.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-separator.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-shortcut.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-sub-content.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-sub-trigger.component.ts +2 -1
- package/src/app/lib/components/ui/context-menu/context-menu-sub.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/context-menu/context-menu.component.ts +1 -0
- package/src/app/lib/components/ui/data-table/data-table-content.component.ts +1 -0
- package/src/app/lib/components/ui/data-table/data-table-pagination.component.ts +1 -0
- package/src/app/lib/components/ui/data-table/data-table-search.component.ts +1 -0
- package/src/app/lib/components/ui/data-table/data-table-toolbar.component.ts +1 -0
- package/src/app/lib/components/ui/data-table/data-table-view-options.component.ts +1 -0
- package/src/app/lib/components/ui/data-table/data-table.component.ts +1 -1
- package/src/app/lib/components/ui/dialog/dialog-close.component.ts +1 -0
- package/src/app/lib/components/ui/dialog/dialog-content.component.ts +20 -16
- package/src/app/lib/components/ui/dialog/dialog-description.component.ts +1 -0
- package/src/app/lib/components/ui/dialog/dialog-footer.component.ts +1 -0
- package/src/app/lib/components/ui/dialog/dialog-header.component.ts +1 -0
- package/src/app/lib/components/ui/dialog/dialog-title.component.ts +1 -0
- package/src/app/lib/components/ui/dialog/dialog-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/dialog/dialog.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer-close.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer-content.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer-description.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer-footer.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer-header.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer-title.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/drawer/drawer.component.ts +4 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-content.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-group.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-item.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-label.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-separator.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.component.ts +2 -1
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu.component.ts +1 -0
- package/src/app/lib/components/ui/hover-card/hover-card-content.component.ts +1 -0
- package/src/app/lib/components/ui/hover-card/hover-card-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/hover-card/hover-card.component.ts +1 -0
- package/src/app/lib/components/ui/input-otp/input-otp-group.component.ts +1 -0
- package/src/app/lib/components/ui/input-otp/input-otp-separator.component.ts +1 -0
- package/src/app/lib/components/ui/input-otp/input-otp-slot.component.ts +1 -0
- package/src/app/lib/components/ui/input-otp/input-otp.component.ts +1 -0
- package/src/app/lib/components/ui/kbd/kbd.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-checkbox-item.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-content.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-item.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-label.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-menu.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-radio-group.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-radio-item.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-separator.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-shortcut.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-sub-content.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-sub-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-sub.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar.component.ts +1 -0
- package/src/app/lib/components/ui/native-select/native-select.component.ts +1 -1
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-content.component.ts +1 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-indicator.component.ts +1 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-item.component.ts +1 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-link.component.ts +1 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-list.component.ts +1 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-viewport.component.ts +1 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu.component.ts +4 -0
- package/src/app/lib/components/ui/pagination/pagination-content.component.ts +1 -0
- package/src/app/lib/components/ui/pagination/pagination-ellipsis.component.ts +1 -0
- package/src/app/lib/components/ui/pagination/pagination-item.component.ts +1 -0
- package/src/app/lib/components/ui/pagination/pagination-link.component.ts +1 -0
- package/src/app/lib/components/ui/pagination/pagination-next.component.ts +1 -0
- package/src/app/lib/components/ui/pagination/pagination-previous.component.ts +1 -0
- package/src/app/lib/components/ui/pagination/pagination.component.ts +1 -0
- package/src/app/lib/components/ui/popover/popover-anchor.component.ts +1 -0
- package/src/app/lib/components/ui/popover/popover-content.component.ts +1 -0
- package/src/app/lib/components/ui/popover/popover-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/popover/popover.component.ts +1 -0
- package/src/app/lib/components/ui/resizable/resizable-handle.component.ts +1 -0
- package/src/app/lib/components/ui/resizable/resizable-panel-group.component.ts +1 -0
- package/src/app/lib/components/ui/resizable/resizable-panel.component.ts +1 -0
- package/src/app/lib/components/ui/scroll-area/scroll-area.component.ts +1 -0
- package/src/app/lib/components/ui/scroll-area/scroll-bar.component.ts +1 -0
- package/src/app/lib/components/ui/select/select-content.component.ts +3 -2
- package/src/app/lib/components/ui/sheet/sheet-close.component.ts +1 -0
- package/src/app/lib/components/ui/sheet/sheet-content.component.ts +1 -0
- package/src/app/lib/components/ui/sheet/sheet-description.component.ts +1 -0
- package/src/app/lib/components/ui/sheet/sheet-footer.component.ts +1 -0
- package/src/app/lib/components/ui/sheet/sheet-header.component.ts +1 -0
- package/src/app/lib/components/ui/sheet/sheet-title.component.ts +1 -0
- package/src/app/lib/components/ui/sheet/sheet-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/sheet/sheet.component.ts +4 -0
- package/src/app/lib/components/ui/sidebar/sidebar-content.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-footer.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-group-action.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-group-content.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-group-label.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-group.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-header.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-input.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-inset.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-action.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-badge.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-button.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-item.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-skeleton.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-sub-button.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-sub-item.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu-sub.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-menu.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-provider.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-rail.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-separator.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/sidebar/sidebar.component.ts +1 -0
- package/src/app/lib/components/ui/spinner/spinner.component.ts +1 -0
- package/src/app/lib/components/ui/table/table-body.component.ts +1 -0
- package/src/app/lib/components/ui/table/table-caption.component.ts +1 -0
- package/src/app/lib/components/ui/table/table-cell.component.ts +1 -0
- package/src/app/lib/components/ui/table/table-footer.component.ts +1 -0
- package/src/app/lib/components/ui/table/table-head.component.ts +1 -0
- package/src/app/lib/components/ui/table/table-header.component.ts +1 -0
- package/src/app/lib/components/ui/table/table-row.component.ts +1 -0
- package/src/app/lib/components/ui/table/table.component.ts +1 -0
- package/src/app/lib/components/ui/tabs/tabs-content.component.ts +3 -6
- package/src/app/lib/components/ui/tabs/tabs-list.component.ts +1 -0
- package/src/app/lib/components/ui/tabs/tabs-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/tabs/tabs.component.ts +1 -0
- package/src/app/lib/components/ui/toast/toast-action.component.ts +1 -0
- package/src/app/lib/components/ui/toast/toast-description.component.ts +1 -0
- package/src/app/lib/components/ui/toast/toast-title.component.ts +1 -0
- package/src/app/lib/components/ui/toast/toast.component.ts +1 -0
- package/src/app/lib/components/ui/toast/toaster.component.ts +1 -0
- package/src/app/lib/components/ui/toggle/toggle-variants.ts +1 -1
- package/src/app/lib/components/ui/tooltip/tooltip-content.component.ts +1 -0
- package/src/app/lib/components/ui/tooltip/tooltip-provider.component.ts +4 -0
- package/src/app/lib/components/ui/tooltip/tooltip-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/tooltip/tooltip.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-blockquote.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-h1.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-h2.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-h3.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-h4.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-inline-code.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-large.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-lead.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-list.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-muted.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-p.component.ts +1 -0
- package/src/app/lib/components/ui/typography/typography-small.component.ts +1 -0
package/package.json
CHANGED
|
@@ -4,7 +4,8 @@ import { ACCORDION_ITEM_CONTEXT } from './accordion-context';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* AccordionContent component - expandable content area.
|
|
7
|
-
* Uses
|
|
7
|
+
* Uses CSS grid animation (grid-template-rows: 0fr → 1fr) for smooth open/close.
|
|
8
|
+
* Content is always in the DOM; visibility is controlled by the grid row height.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
11
|
* <AccordionContent>
|
|
@@ -14,19 +15,17 @@ import { ACCORDION_ITEM_CONTEXT } from './accordion-context';
|
|
|
14
15
|
@Component({
|
|
15
16
|
selector: 'AccordionContent',
|
|
16
17
|
template: `
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
</div>
|
|
21
|
-
}
|
|
18
|
+
<div [class]="innerClass()">
|
|
19
|
+
<ng-content />
|
|
20
|
+
</div>
|
|
22
21
|
`,
|
|
23
22
|
host: {
|
|
23
|
+
'attr.data-slot': '"accordion-content"',
|
|
24
24
|
role: 'region',
|
|
25
25
|
'[class]': 'computedClass()',
|
|
26
26
|
'[attr.id]': 'item.contentId',
|
|
27
27
|
'[attr.data-state]': 'item.isOpen() ? "open" : "closed"',
|
|
28
28
|
'[attr.aria-labelledby]': 'item.triggerId',
|
|
29
|
-
'[attr.aria-hidden]': '!item.isOpen()',
|
|
30
29
|
},
|
|
31
30
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
32
31
|
})
|
|
@@ -36,13 +35,11 @@ export class AccordionContent {
|
|
|
36
35
|
|
|
37
36
|
protected readonly item = inject(ACCORDION_ITEM_CONTEXT);
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
),
|
|
44
|
-
);
|
|
38
|
+
// No overflow-hidden on the host — grid-template-rows handles expansion.
|
|
39
|
+
// Global CSS ([data-slot='accordion-content'] > div { overflow: hidden }) clips the inner div.
|
|
40
|
+
protected readonly computedClass = computed(() => cn('text-sm', this.class()));
|
|
41
|
+
|
|
45
42
|
protected readonly innerClass = computed(() =>
|
|
46
|
-
cn('pb-4 pt-2 px-1 text-muted-foreground leading-relaxed'
|
|
43
|
+
cn('pb-4 pt-2 px-1 text-muted-foreground leading-relaxed'),
|
|
47
44
|
);
|
|
48
45
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cn } from '@/lib/utils';
|
|
2
2
|
import { AriaIdService } from '@/lib/utils/accessibility';
|
|
3
3
|
import {
|
|
4
|
+
booleanAttribute,
|
|
4
5
|
ChangeDetectionStrategy,
|
|
5
6
|
Component,
|
|
6
7
|
computed,
|
|
@@ -30,9 +31,12 @@ import {
|
|
|
30
31
|
selector: 'AccordionItem',
|
|
31
32
|
template: `<ng-content />`,
|
|
32
33
|
host: {
|
|
34
|
+
'attr.data-slot': '"accordion-item"',
|
|
33
35
|
'[class]': 'computedClass()',
|
|
34
36
|
'[attr.data-state]': 'isOpen() ? "open" : "closed"',
|
|
35
37
|
'[attr.data-value]': 'value()',
|
|
38
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
39
|
+
'[attr.aria-disabled]': 'disabled() || null',
|
|
36
40
|
},
|
|
37
41
|
providers: [
|
|
38
42
|
{
|
|
@@ -46,6 +50,9 @@ export class AccordionItem implements AccordionItemContext, OnInit, OnDestroy {
|
|
|
46
50
|
/** Unique value for this accordion item */
|
|
47
51
|
readonly value = input.required<string>();
|
|
48
52
|
|
|
53
|
+
/** Whether this item is disabled */
|
|
54
|
+
readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });
|
|
55
|
+
|
|
49
56
|
/** Additional CSS classes */
|
|
50
57
|
readonly class = input<string>('');
|
|
51
58
|
|
|
@@ -77,6 +84,7 @@ export class AccordionItem implements AccordionItemContext, OnInit, OnDestroy {
|
|
|
77
84
|
|
|
78
85
|
/** Toggle this item's open state */
|
|
79
86
|
toggle(): void {
|
|
87
|
+
if (this.disabled()) return;
|
|
80
88
|
this._accordion.onValueChange(this.value());
|
|
81
89
|
}
|
|
82
90
|
}
|
|
@@ -15,7 +15,6 @@ import { ACCORDION_ITEM_CONTEXT } from './accordion-context';
|
|
|
15
15
|
<span class="me-2"><ng-content /></span>
|
|
16
16
|
<svg
|
|
17
17
|
class="size-4 shrink-0 ms-auto text-muted-foreground transition-transform duration-200"
|
|
18
|
-
[class.rotate-180]="item.isOpen()"
|
|
19
18
|
xmlns="http://www.w3.org/2000/svg"
|
|
20
19
|
width="24"
|
|
21
20
|
height="24"
|
|
@@ -30,11 +29,13 @@ import { ACCORDION_ITEM_CONTEXT } from './accordion-context';
|
|
|
30
29
|
</svg>
|
|
31
30
|
`,
|
|
32
31
|
host: {
|
|
32
|
+
'attr.data-slot': '"accordion-trigger"',
|
|
33
33
|
'[class]': 'computedClass()',
|
|
34
34
|
'[attr.id]': 'item.triggerId',
|
|
35
35
|
'[attr.data-state]': 'item.isOpen() ? "open" : "closed"',
|
|
36
36
|
'[attr.aria-expanded]': 'item.isOpen()',
|
|
37
37
|
'[attr.aria-controls]': 'item.contentId',
|
|
38
|
+
'[attr.aria-disabled]': 'item.disabled() || null',
|
|
38
39
|
'(click)': 'onClick()',
|
|
39
40
|
'(keydown.enter)': 'onClick()',
|
|
40
41
|
'(keydown.space)': 'onSpace($event)',
|
|
@@ -52,15 +53,18 @@ export class AccordionTrigger {
|
|
|
52
53
|
protected readonly computedClass = computed(() =>
|
|
53
54
|
cn(
|
|
54
55
|
'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180 cursor-pointer w-full',
|
|
56
|
+
this.item.disabled() && 'cursor-not-allowed opacity-50 hover:no-underline',
|
|
55
57
|
this.class(),
|
|
56
58
|
),
|
|
57
59
|
);
|
|
58
60
|
|
|
59
61
|
protected onClick(): void {
|
|
62
|
+
if (this.item.disabled()) return;
|
|
60
63
|
this.item.toggle();
|
|
61
64
|
}
|
|
62
65
|
protected onSpace(event: Event): void {
|
|
63
66
|
event.preventDefault();
|
|
67
|
+
if (this.item.disabled()) return;
|
|
64
68
|
this.item.toggle();
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
effect,
|
|
8
8
|
forwardRef,
|
|
9
9
|
input,
|
|
10
|
+
output,
|
|
10
11
|
signal,
|
|
11
12
|
} from '@angular/core';
|
|
12
13
|
import { ACCORDION_CONTEXT, AccordionContext, AccordionType } from './accordion-context';
|
|
@@ -28,6 +29,7 @@ import { ACCORDION_CONTEXT, AccordionContext, AccordionType } from './accordion-
|
|
|
28
29
|
selector: 'Accordion',
|
|
29
30
|
template: `<ng-content />`,
|
|
30
31
|
host: {
|
|
32
|
+
'attr.data-slot': '"accordion"',
|
|
31
33
|
'[class]': 'computedClass()',
|
|
32
34
|
'[attr.data-orientation]': '"vertical"',
|
|
33
35
|
'(keydown)': 'onKeydown($event)',
|
|
@@ -69,6 +71,9 @@ export class Accordion implements AccordionContext {
|
|
|
69
71
|
/** Additional CSS classes */
|
|
70
72
|
readonly class = input<string>('');
|
|
71
73
|
|
|
74
|
+
/** Emits the new value whenever the open state changes */
|
|
75
|
+
readonly valueChange = output<string | string[] | undefined>();
|
|
76
|
+
|
|
72
77
|
protected readonly computedClass = computed(() => cn('w-full', this.class()));
|
|
73
78
|
/** Get current value(s) */
|
|
74
79
|
readonly value = computed(() => {
|
|
@@ -119,6 +124,7 @@ export class Accordion implements AccordionContext {
|
|
|
119
124
|
|
|
120
125
|
return newSet;
|
|
121
126
|
});
|
|
127
|
+
this.valueChange.emit(this.value());
|
|
122
128
|
}
|
|
123
129
|
/** Check if an item is open */
|
|
124
130
|
isItemOpen(itemValue: string): boolean {
|
|
@@ -179,12 +185,24 @@ export class Accordion implements AccordionContext {
|
|
|
179
185
|
|
|
180
186
|
if (handled) {
|
|
181
187
|
event.preventDefault();
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
const direction =
|
|
189
|
+
event.key === 'ArrowDown' || event.key === 'End' ? 1 : -1;
|
|
190
|
+
let attempts = 0;
|
|
191
|
+
while (attempts < values.length) {
|
|
192
|
+
const candidateValue = values[newIndex];
|
|
193
|
+
const candidateItem = document.querySelector(
|
|
194
|
+
`AccordionItem[data-value="${candidateValue}"]`,
|
|
195
|
+
);
|
|
196
|
+
if (!candidateItem?.hasAttribute('data-disabled')) {
|
|
197
|
+
const trigger = document.querySelector(
|
|
198
|
+
`AccordionItem[data-value="${candidateValue}"] AccordionTrigger`,
|
|
199
|
+
) as HTMLElement;
|
|
200
|
+
trigger?.focus();
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
newIndex = (newIndex + direction + values.length) % values.length;
|
|
204
|
+
attempts++;
|
|
205
|
+
}
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
208
|
}
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Alert variants using class-variance-authority
|
|
5
|
-
*
|
|
4
|
+
* Alert variants using class-variance-authority.
|
|
5
|
+
* Selectors cover both native <svg> and lucide-angular's <lucide-icon> element.
|
|
6
6
|
*/
|
|
7
7
|
export const alertVariants = cva(
|
|
8
|
-
|
|
8
|
+
[
|
|
9
|
+
'relative w-full rounded-lg border px-4 py-3 text-sm',
|
|
10
|
+
// Grid base — 1-col by default, switches to 2-col when icon is present
|
|
11
|
+
'grid grid-cols-[0_1fr]',
|
|
12
|
+
'has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr]',
|
|
13
|
+
'has-[>lucide-icon]:grid-cols-[calc(var(--spacing)*4)_1fr]',
|
|
14
|
+
// Column gap when icon present
|
|
15
|
+
'has-[>svg]:gap-x-3',
|
|
16
|
+
'has-[>lucide-icon]:gap-x-3',
|
|
17
|
+
'gap-y-0.5 items-start',
|
|
18
|
+
// Icon sizing and alignment — native SVG
|
|
19
|
+
'[&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
|
|
20
|
+
// Icon sizing and alignment — lucide-angular
|
|
21
|
+
'[&>lucide-icon]:size-4 [&>lucide-icon]:translate-y-0.5 [&>lucide-icon]:text-current',
|
|
22
|
+
].join(' '),
|
|
9
23
|
{
|
|
10
24
|
variants: {
|
|
11
25
|
variant: {
|
|
12
26
|
default: 'bg-card text-card-foreground border-border',
|
|
13
27
|
destructive:
|
|
14
|
-
'bg-destructive/10 text-destructive border-destructive/30 [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
|
|
28
|
+
'bg-destructive/10 text-destructive border-destructive/30 [&>svg]:text-current [&>lucide-icon]:text-current *:data-[slot=alert-description]:text-destructive/90',
|
|
15
29
|
},
|
|
16
30
|
},
|
|
17
31
|
defaultVariants: {
|
|
@@ -18,7 +18,7 @@ import { ALERT_DIALOG_CONTEXT } from './alert-dialog-context';
|
|
|
18
18
|
host: {
|
|
19
19
|
'[class]': 'computedClass()',
|
|
20
20
|
'(click)': 'onClick($event)',
|
|
21
|
-
'data-slot': 'alert-dialog-cancel',
|
|
21
|
+
'attr.data-slot': '"alert-dialog-cancel"',
|
|
22
22
|
},
|
|
23
23
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
24
24
|
})
|
|
@@ -2,6 +2,7 @@ import { cn } from '@/lib/utils';
|
|
|
2
2
|
import { FocusTrapDirective } from '@/lib/utils/accessibility';
|
|
3
3
|
import {
|
|
4
4
|
ChangeDetectionStrategy,
|
|
5
|
+
ChangeDetectorRef,
|
|
5
6
|
Component,
|
|
6
7
|
computed,
|
|
7
8
|
DestroyRef,
|
|
@@ -9,9 +10,13 @@ import {
|
|
|
9
10
|
HostListener,
|
|
10
11
|
inject,
|
|
11
12
|
input,
|
|
13
|
+
signal,
|
|
12
14
|
} from '@angular/core';
|
|
13
15
|
import { ALERT_DIALOG_CONTEXT } from './alert-dialog-context';
|
|
14
16
|
|
|
17
|
+
/** Animation duration in ms — must match Tailwind's duration-200 */
|
|
18
|
+
const EXIT_ANIMATION_MS = 200;
|
|
19
|
+
|
|
15
20
|
/**
|
|
16
21
|
* AlertDialogContent component - the modal content of the alert dialog.
|
|
17
22
|
* Matches shadcn/ui React AlertDialogContent exactly.
|
|
@@ -26,9 +31,13 @@ import { ALERT_DIALOG_CONTEXT } from './alert-dialog-context';
|
|
|
26
31
|
selector: 'AlertDialogContent',
|
|
27
32
|
imports: [FocusTrapDirective],
|
|
28
33
|
template: `
|
|
29
|
-
@if (
|
|
34
|
+
@if (shouldRender()) {
|
|
30
35
|
<!-- Overlay - does NOT close on click -->
|
|
31
|
-
<div
|
|
36
|
+
<div
|
|
37
|
+
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 duration-200"
|
|
38
|
+
[attr.data-state]="context.isOpen() ? 'open' : 'closed'"
|
|
39
|
+
aria-hidden="true"
|
|
40
|
+
></div>
|
|
32
41
|
<!-- Content Dialog -->
|
|
33
42
|
<div
|
|
34
43
|
hlmFocusTrap
|
|
@@ -37,6 +46,7 @@ import { ALERT_DIALOG_CONTEXT } from './alert-dialog-context';
|
|
|
37
46
|
[restoreFocus]="false"
|
|
38
47
|
[initialFocus]="'[data-slot=alert-dialog-cancel]'"
|
|
39
48
|
[class]="computedClass()"
|
|
49
|
+
[attr.data-state]="context.isOpen() ? 'open' : 'closed'"
|
|
40
50
|
[attr.id]="context.contentId"
|
|
41
51
|
[attr.aria-labelledby]="context.titleId"
|
|
42
52
|
[attr.aria-describedby]="context.descriptionId"
|
|
@@ -48,35 +58,44 @@ import { ALERT_DIALOG_CONTEXT } from './alert-dialog-context';
|
|
|
48
58
|
}
|
|
49
59
|
`,
|
|
50
60
|
host: {
|
|
61
|
+
'attr.data-slot': '"alert-dialog-content"',
|
|
51
62
|
class: 'contents',
|
|
52
63
|
},
|
|
53
64
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
54
65
|
})
|
|
55
66
|
export class AlertDialogContent {
|
|
56
67
|
constructor() {
|
|
57
|
-
// Handle body scroll lock based on open state
|
|
58
68
|
effect(() => {
|
|
59
69
|
const isOpen = this.context.isOpen();
|
|
70
|
+
this._cdr.markForCheck();
|
|
71
|
+
|
|
60
72
|
if (isOpen) {
|
|
73
|
+
this.shouldRender.set(true);
|
|
61
74
|
this.lockBodyScroll();
|
|
62
75
|
} else {
|
|
63
76
|
this.unlockBodyScroll();
|
|
77
|
+
if (this.shouldRender()) {
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
this.shouldRender.set(false);
|
|
80
|
+
this._cdr.markForCheck();
|
|
81
|
+
}, EXIT_ANIMATION_MS);
|
|
82
|
+
}
|
|
64
83
|
}
|
|
65
84
|
});
|
|
66
85
|
|
|
67
|
-
// Cleanup on destroy
|
|
68
86
|
this._destroyRef.onDestroy(() => {
|
|
69
87
|
this.unlockBodyScroll();
|
|
70
88
|
this.restoreFocus();
|
|
71
89
|
});
|
|
72
90
|
}
|
|
73
91
|
|
|
74
|
-
/** Additional CSS classes */
|
|
75
92
|
readonly class = input<string>('');
|
|
76
93
|
|
|
77
94
|
private readonly _destroyRef = inject(DestroyRef);
|
|
95
|
+
private readonly _cdr = inject(ChangeDetectorRef);
|
|
78
96
|
|
|
79
97
|
protected readonly context = inject(ALERT_DIALOG_CONTEXT);
|
|
98
|
+
protected readonly shouldRender = signal(false);
|
|
80
99
|
|
|
81
100
|
protected readonly computedClass = computed(() =>
|
|
82
101
|
cn(
|
|
@@ -91,7 +110,6 @@ export class AlertDialogContent {
|
|
|
91
110
|
),
|
|
92
111
|
);
|
|
93
112
|
|
|
94
|
-
/** Previous body overflow for restoration */
|
|
95
113
|
private previousBodyOverflow = '';
|
|
96
114
|
|
|
97
115
|
@HostListener('document:keydown.escape')
|
|
@@ -119,9 +137,7 @@ export class AlertDialogContent {
|
|
|
119
137
|
private restoreFocus(): void {
|
|
120
138
|
const triggerEl = this.context.getTriggerElement();
|
|
121
139
|
if (triggerEl) {
|
|
122
|
-
setTimeout(() =>
|
|
123
|
-
triggerEl.focus();
|
|
124
|
-
}, 0);
|
|
140
|
+
setTimeout(() => triggerEl.focus(), 0);
|
|
125
141
|
}
|
|
126
142
|
}
|
|
127
143
|
}
|
|
@@ -14,6 +14,7 @@ import { ALERT_DIALOG_CONTEXT } from './alert-dialog-context';
|
|
|
14
14
|
selector: 'AlertDialogDescription',
|
|
15
15
|
template: `<ng-content />`,
|
|
16
16
|
host: {
|
|
17
|
+
'attr.data-slot': '"alert-dialog-description"',
|
|
17
18
|
'[class]': 'computedClass()',
|
|
18
19
|
'[attr.id]': 'context.descriptionId',
|
|
19
20
|
},
|
|
@@ -16,6 +16,7 @@ import { ChangeDetectionStrategy, Component, computed, input } from '@angular/co
|
|
|
16
16
|
selector: 'AlertDialogFooter',
|
|
17
17
|
template: `<ng-content />`,
|
|
18
18
|
host: {
|
|
19
|
+
'attr.data-slot': '"alert-dialog-footer"',
|
|
19
20
|
'[class]': 'computedClass()',
|
|
20
21
|
},
|
|
21
22
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -16,6 +16,7 @@ import { ChangeDetectionStrategy, Component, computed, input } from '@angular/co
|
|
|
16
16
|
selector: 'AlertDialogHeader',
|
|
17
17
|
template: `<ng-content />`,
|
|
18
18
|
host: {
|
|
19
|
+
'attr.data-slot': '"alert-dialog-header"',
|
|
19
20
|
'[class]': 'computedClass()',
|
|
20
21
|
},
|
|
21
22
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -15,6 +15,7 @@ import { ALERT_DIALOG_CONTEXT } from './alert-dialog-context';
|
|
|
15
15
|
selector: 'AlertDialogTrigger',
|
|
16
16
|
template: `<ng-content />`,
|
|
17
17
|
host: {
|
|
18
|
+
'attr.data-slot': '"alert-dialog-trigger"',
|
|
18
19
|
'(click)': 'onClick($event)',
|
|
19
20
|
'[attr.aria-haspopup]': '"dialog"',
|
|
20
21
|
'[attr.aria-expanded]': 'context.isOpen()',
|
|
@@ -43,6 +43,10 @@ import { ALERT_DIALOG_CONTEXT, type AlertDialogContextValue } from './alert-dial
|
|
|
43
43
|
useExisting: forwardRef(() => AlertDialog),
|
|
44
44
|
},
|
|
45
45
|
],
|
|
46
|
+
host: {
|
|
47
|
+
'attr.data-slot': '"alert-dialog"',
|
|
48
|
+
style: 'display: contents',
|
|
49
|
+
},
|
|
46
50
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
47
51
|
})
|
|
48
52
|
export class AlertDialog implements AlertDialogContextValue {
|
|
@@ -30,6 +30,7 @@ import { ChangeDetectionStrategy, Component, computed, input } from '@angular/co
|
|
|
30
30
|
</div>
|
|
31
31
|
`,
|
|
32
32
|
host: {
|
|
33
|
+
'attr.data-slot': '"aspect-ratio"',
|
|
33
34
|
'[class]': 'computedClass()',
|
|
34
35
|
'[style.padding-bottom]': 'paddingBottom()',
|
|
35
36
|
'[style.position]': '"relative"',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { InjectionToken, WritableSignal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type AvatarImageStatus = 'idle' | 'loaded' | 'error';
|
|
4
|
+
|
|
5
|
+
export interface AvatarContext {
|
|
6
|
+
imageStatus: WritableSignal<AvatarImageStatus>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AVATAR_CONTEXT = new InjectionToken<AvatarContext>('AvatarContext');
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import { cn } from '@/lib/utils';
|
|
2
|
-
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
2
|
+
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
|
|
3
|
+
import { AVATAR_CONTEXT } from './avatar-context';
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* Avatar fallback component.
|
|
6
|
-
* Shown when the image fails to load or is not provided.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* <AvatarFallback>JD</AvatarFallback>
|
|
10
|
-
*/
|
|
11
5
|
@Component({
|
|
12
6
|
selector: 'AvatarFallback',
|
|
13
7
|
template: `<ng-content />`,
|
|
14
8
|
host: {
|
|
9
|
+
'attr.data-slot': '"avatar-fallback"',
|
|
15
10
|
'[class]': 'computedClass()',
|
|
16
|
-
|
|
11
|
+
// Hidden via display:none when image has loaded; always in DOM for layout stability
|
|
12
|
+
'[style.display]': 'context.imageStatus() === "loaded" ? "none" : null',
|
|
17
13
|
},
|
|
18
14
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19
15
|
})
|
|
20
16
|
export class AvatarFallback {
|
|
21
17
|
readonly class = input<string>('');
|
|
18
|
+
protected readonly context = inject(AVATAR_CONTEXT);
|
|
22
19
|
|
|
23
20
|
protected readonly computedClass = computed(() =>
|
|
24
21
|
cn('bg-muted flex size-full items-center justify-center rounded-full text-xs', this.class()),
|
|
@@ -1,24 +1,53 @@
|
|
|
1
1
|
import { cn } from '@/lib/utils';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
Component,
|
|
5
|
+
computed,
|
|
6
|
+
effect,
|
|
7
|
+
inject,
|
|
8
|
+
input,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { AVATAR_CONTEXT } from './avatar-context';
|
|
3
11
|
|
|
4
|
-
/**
|
|
5
|
-
* Avatar image component.
|
|
6
|
-
* The image to display for the avatar.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* <AvatarImage src="/avatar.png" alt="User avatar" />
|
|
10
|
-
*/
|
|
11
12
|
@Component({
|
|
12
13
|
selector: 'AvatarImage',
|
|
13
|
-
template:
|
|
14
|
+
template: `
|
|
15
|
+
@if (context.imageStatus() !== 'error') {
|
|
16
|
+
<img
|
|
17
|
+
[src]="src()"
|
|
18
|
+
[alt]="alt()"
|
|
19
|
+
[class]="computedClass()"
|
|
20
|
+
(load)="onLoad()"
|
|
21
|
+
(error)="onError()"
|
|
22
|
+
/>
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
14
25
|
host: {
|
|
15
|
-
'
|
|
16
|
-
'data-slot': 'avatar-image',
|
|
26
|
+
'attr.data-slot': '"avatar-image"',
|
|
17
27
|
},
|
|
18
28
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19
29
|
})
|
|
20
30
|
export class AvatarImage {
|
|
31
|
+
readonly src = input<string>('');
|
|
32
|
+
readonly alt = input<string>('');
|
|
21
33
|
readonly class = input<string>('');
|
|
22
34
|
|
|
35
|
+
protected readonly context = inject(AVATAR_CONTEXT);
|
|
23
36
|
protected readonly computedClass = computed(() => cn('aspect-square size-full', this.class()));
|
|
37
|
+
|
|
38
|
+
constructor() {
|
|
39
|
+
// Reset status whenever src changes so a new src gets a fresh load attempt
|
|
40
|
+
effect(() => {
|
|
41
|
+
const _ = this.src();
|
|
42
|
+
this.context.imageStatus.set('idle');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected onLoad(): void {
|
|
47
|
+
this.context.imageStatus.set('loaded');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected onError(): void {
|
|
51
|
+
this.context.imageStatus.set('error');
|
|
52
|
+
}
|
|
24
53
|
}
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
import { cn } from '@/lib/utils';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
Component,
|
|
5
|
+
computed,
|
|
6
|
+
forwardRef,
|
|
7
|
+
input,
|
|
8
|
+
signal,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { AVATAR_CONTEXT, AvatarContext, AvatarImageStatus } from './avatar-context';
|
|
3
11
|
|
|
4
|
-
/**
|
|
5
|
-
* Avatar container component.
|
|
6
|
-
* Wraps the avatar image and fallback.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* <Avatar>
|
|
10
|
-
* <img AvatarImage src="/avatar.png" alt="User" />
|
|
11
|
-
* <AvatarFallback>JD</AvatarFallback>
|
|
12
|
-
* </Avatar>
|
|
13
|
-
*/
|
|
14
12
|
@Component({
|
|
15
13
|
selector: 'Avatar',
|
|
16
14
|
template: `<ng-content />`,
|
|
17
15
|
host: {
|
|
16
|
+
'attr.data-slot': '"avatar"',
|
|
18
17
|
'[class]': 'computedClass()',
|
|
19
|
-
'data-slot': 'avatar',
|
|
20
18
|
},
|
|
19
|
+
providers: [
|
|
20
|
+
{
|
|
21
|
+
provide: AVATAR_CONTEXT,
|
|
22
|
+
useExisting: forwardRef(() => Avatar),
|
|
23
|
+
},
|
|
24
|
+
],
|
|
21
25
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
22
26
|
})
|
|
23
|
-
export class Avatar {
|
|
27
|
+
export class Avatar implements AvatarContext {
|
|
24
28
|
readonly class = input<string>('');
|
|
29
|
+
readonly imageStatus = signal<AvatarImageStatus>('idle');
|
|
25
30
|
|
|
26
31
|
protected readonly computedClass = computed(() =>
|
|
27
32
|
cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', this.class()),
|
|
@@ -1,30 +1,25 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, input
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
2
2
|
import { AvatarFallback } from './avatar-fallback.component';
|
|
3
|
+
import { AvatarImage } from './avatar-image.component';
|
|
3
4
|
import { Avatar } from './avatar.component';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Convenience component combining Avatar + AvatarImage + AvatarFallback.
|
|
8
|
+
* Error handling is automatic via the shared Avatar context.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
|
-
* <ui-avatar
|
|
11
|
-
* src="/avatar.png"
|
|
12
|
-
* alt="John Doe"
|
|
13
|
-
* fallback="JD"
|
|
14
|
-
* />
|
|
11
|
+
* <ui-avatar src="/avatar.png" alt="John Doe" fallback="JD" />
|
|
15
12
|
*/
|
|
16
13
|
@Component({
|
|
17
14
|
selector: 'ui-avatar',
|
|
18
15
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19
|
-
imports: [Avatar, AvatarFallback],
|
|
16
|
+
imports: [Avatar, AvatarImage, AvatarFallback],
|
|
20
17
|
template: `
|
|
21
18
|
<Avatar [class]="class()">
|
|
22
|
-
@if (src()
|
|
23
|
-
<
|
|
24
|
-
}
|
|
25
|
-
@if (!src() || imageError()) {
|
|
26
|
-
<AvatarFallback>{{ fallback() }}</AvatarFallback>
|
|
19
|
+
@if (src()) {
|
|
20
|
+
<AvatarImage [src]="src()" [alt]="alt()" />
|
|
27
21
|
}
|
|
22
|
+
<AvatarFallback>{{ fallback() }}</AvatarFallback>
|
|
28
23
|
</Avatar>
|
|
29
24
|
`,
|
|
30
25
|
})
|
|
@@ -33,10 +28,4 @@ export class UiAvatar {
|
|
|
33
28
|
readonly alt = input<string>('');
|
|
34
29
|
readonly fallback = input<string>('');
|
|
35
30
|
readonly class = input<string>('');
|
|
36
|
-
|
|
37
|
-
protected readonly imageError = signal(false);
|
|
38
|
-
|
|
39
|
-
protected onImageError(): void {
|
|
40
|
-
this.imageError.set(true);
|
|
41
|
-
}
|
|
42
31
|
}
|