@surfnet/curve-angular 0.2.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/.postcssrc.json +7 -0
- package/.storybook/main.ts +9 -0
- package/.storybook/manager.ts +3 -0
- package/.storybook/preview.ts +28 -0
- package/.storybook/tsconfig.json +8 -0
- package/.turbo/turbo-build.log +32 -0
- package/CHANGELOG.md +29 -0
- package/angular.json +52 -0
- package/components.json +5 -0
- package/dist/fesm2022/surfnet-curve-angular.mjs +4237 -0
- package/dist/fesm2022/surfnet-curve-angular.mjs.map +1 -0
- package/dist/files/geist-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/files/geist-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/files/geist-cyrillic-wght-italic.woff2 +0 -0
- package/dist/files/geist-cyrillic-wght-normal.woff2 +0 -0
- package/dist/files/geist-latin-ext-wght-italic.woff2 +0 -0
- package/dist/files/geist-latin-ext-wght-normal.woff2 +0 -0
- package/dist/files/geist-latin-wght-italic.woff2 +0 -0
- package/dist/files/geist-latin-wght-normal.woff2 +0 -0
- package/dist/files/geist-vietnamese-wght-italic.woff2 +0 -0
- package/dist/files/geist-vietnamese-wght-normal.woff2 +0 -0
- package/dist/styles.css +2 -0
- package/dist/types/surfnet-curve-angular.d.ts +1017 -0
- package/ng-package.json +20 -0
- package/package.json +73 -0
- package/scripts/copy-font-files.ts +15 -0
- package/scripts/rewrite-helm-imports.ts +60 -0
- package/src/foundations/design-tokens.stories.ts +198 -0
- package/src/lib/ui/avatar/src/index.ts +22 -0
- package/src/lib/ui/avatar/src/lib/hlm-avatar-badge.ts +19 -0
- package/src/lib/ui/avatar/src/lib/hlm-avatar-fallback.ts +20 -0
- package/src/lib/ui/avatar/src/lib/hlm-avatar-group-count.ts +17 -0
- package/src/lib/ui/avatar/src/lib/hlm-avatar-group.ts +17 -0
- package/src/lib/ui/avatar/src/lib/hlm-avatar-image.ts +19 -0
- package/src/lib/ui/avatar/src/lib/hlm-avatar.stories.ts +107 -0
- package/src/lib/ui/avatar/src/lib/hlm-avatar.ts +32 -0
- package/src/lib/ui/breadcrumb/src/index.ts +30 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-ellipsis.ts +36 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-item.ts +14 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-link.ts +36 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-list.ts +17 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-page.ts +17 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-separator.ts +27 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb.stories.ts +81 -0
- package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb.ts +13 -0
- package/src/lib/ui/button/src/index.ts +6 -0
- package/src/lib/ui/button/src/lib/hlm-button.stories.ts +170 -0
- package/src/lib/ui/button/src/lib/hlm-button.token.ts +22 -0
- package/src/lib/ui/button/src/lib/hlm-button.ts +76 -0
- package/src/lib/ui/card/src/index.ts +25 -0
- package/src/lib/ui/card/src/lib/hlm-card-action.ts +12 -0
- package/src/lib/ui/card/src/lib/hlm-card-content.ts +12 -0
- package/src/lib/ui/card/src/lib/hlm-card-description.ts +12 -0
- package/src/lib/ui/card/src/lib/hlm-card-footer.ts +14 -0
- package/src/lib/ui/card/src/lib/hlm-card-header.ts +15 -0
- package/src/lib/ui/card/src/lib/hlm-card-title.ts +12 -0
- package/src/lib/ui/card/src/lib/hlm-card.stories.ts +105 -0
- package/src/lib/ui/card/src/lib/hlm-card.token.ts +19 -0
- package/src/lib/ui/card/src/lib/hlm-card.ts +22 -0
- package/src/lib/ui/checkbox/src/index.ts +5 -0
- package/src/lib/ui/checkbox/src/lib/hlm-checkbox.stories.ts +78 -0
- package/src/lib/ui/checkbox/src/lib/hlm-checkbox.ts +160 -0
- package/src/lib/ui/data-table/src/index.ts +14 -0
- package/src/lib/ui/data-table/src/lib/hlm-data-table-content.ts +87 -0
- package/src/lib/ui/data-table/src/lib/hlm-data-table-pagination.ts +62 -0
- package/src/lib/ui/data-table/src/lib/hlm-data-table-toolbar.ts +16 -0
- package/src/lib/ui/data-table/src/lib/hlm-data-table.stories.ts +479 -0
- package/src/lib/ui/data-table/src/lib/inject-data-table.ts +86 -0
- package/src/lib/ui/dropdown-menu/src/index.ts +45 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-checkbox-indicator.ts +20 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-checkbox.ts +36 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-group.ts +16 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-item-sub-indicator.ts +17 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-item.ts +41 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-label.ts +20 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-radio-indicator.ts +20 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-radio.ts +36 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-separator.ts +14 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-shortcut.ts +14 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-sub.ts +58 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-token.ts +26 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-trigger.ts +49 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu.stories.ts +125 -0
- package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu.ts +62 -0
- package/src/lib/ui/field/src/index.ts +34 -0
- package/src/lib/ui/field/src/lib/hlm-field-content.ts +12 -0
- package/src/lib/ui/field/src/lib/hlm-field-description.ts +53 -0
- package/src/lib/ui/field/src/lib/hlm-field-error.ts +100 -0
- package/src/lib/ui/field/src/lib/hlm-field-group.ts +15 -0
- package/src/lib/ui/field/src/lib/hlm-field-label.ts +17 -0
- package/src/lib/ui/field/src/lib/hlm-field-legend.ts +17 -0
- package/src/lib/ui/field/src/lib/hlm-field-separator.ts +24 -0
- package/src/lib/ui/field/src/lib/hlm-field-set.ts +15 -0
- package/src/lib/ui/field/src/lib/hlm-field-title.ts +15 -0
- package/src/lib/ui/field/src/lib/hlm-field.stories.ts +117 -0
- package/src/lib/ui/field/src/lib/hlm-field.ts +49 -0
- package/src/lib/ui/icon/src/index.ts +7 -0
- package/src/lib/ui/icon/src/lib/hlm-icon.token.ts +20 -0
- package/src/lib/ui/icon/src/lib/hlm-icon.ts +42 -0
- package/src/lib/ui/input/src/index.ts +5 -0
- package/src/lib/ui/input/src/lib/hlm-input.stories.ts +94 -0
- package/src/lib/ui/input/src/lib/hlm-input.ts +20 -0
- package/src/lib/ui/input-group/src/index.ts +22 -0
- package/src/lib/ui/input-group/src/lib/hlm-input-group-addon.ts +40 -0
- package/src/lib/ui/input-group/src/lib/hlm-input-group-button.ts +47 -0
- package/src/lib/ui/input-group/src/lib/hlm-input-group-input.ts +17 -0
- package/src/lib/ui/input-group/src/lib/hlm-input-group-text.ts +14 -0
- package/src/lib/ui/input-group/src/lib/hlm-input-group-textarea.ts +17 -0
- package/src/lib/ui/input-group/src/lib/hlm-input-group.stories.ts +133 -0
- package/src/lib/ui/input-group/src/lib/hlm-input-group.ts +18 -0
- package/src/lib/ui/label/src/index.ts +5 -0
- package/src/lib/ui/label/src/lib/hlm-label.stories.ts +55 -0
- package/src/lib/ui/label/src/lib/hlm-label.ts +17 -0
- package/src/lib/ui/select/src/index.ts +52 -0
- package/src/lib/ui/select/src/lib/hlm-select-content.ts +44 -0
- package/src/lib/ui/select/src/lib/hlm-select-group.ts +14 -0
- package/src/lib/ui/select/src/lib/hlm-select-item.ts +36 -0
- package/src/lib/ui/select/src/lib/hlm-select-label.ts +14 -0
- package/src/lib/ui/select/src/lib/hlm-select-multiple.ts +45 -0
- package/src/lib/ui/select/src/lib/hlm-select-placeholder.ts +17 -0
- package/src/lib/ui/select/src/lib/hlm-select-portal.ts +8 -0
- package/src/lib/ui/select/src/lib/hlm-select-scroll-down.ts +22 -0
- package/src/lib/ui/select/src/lib/hlm-select-scroll-up.ts +22 -0
- package/src/lib/ui/select/src/lib/hlm-select-separator.ts +14 -0
- package/src/lib/ui/select/src/lib/hlm-select-trigger.ts +59 -0
- package/src/lib/ui/select/src/lib/hlm-select-value-template.ts +5 -0
- package/src/lib/ui/select/src/lib/hlm-select-value.ts +18 -0
- package/src/lib/ui/select/src/lib/hlm-select-values-content.ts +9 -0
- package/src/lib/ui/select/src/lib/hlm-select-values.ts +5 -0
- package/src/lib/ui/select/src/lib/hlm-select.stories.ts +105 -0
- package/src/lib/ui/select/src/lib/hlm-select.ts +45 -0
- package/src/lib/ui/separator/src/index.ts +5 -0
- package/src/lib/ui/separator/src/lib/hlm-separator.stories.ts +88 -0
- package/src/lib/ui/separator/src/lib/hlm-separator.ts +19 -0
- package/src/lib/ui/sheet/src/index.ts +34 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-close.ts +9 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-content.ts +66 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-description.ts +14 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-footer.ts +12 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-header.ts +12 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-overlay.ts +27 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-portal.ts +8 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-title.ts +14 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet-trigger.ts +9 -0
- package/src/lib/ui/sheet/src/lib/hlm-sheet.ts +29 -0
- package/src/lib/ui/sidebar/src/index.ts +75 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-content.ts +18 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-footer.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group-action.ts +18 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group-content.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group-label.ts +18 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-header.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-input.ts +17 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-inset.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-action.ts +24 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-badge.ts +18 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-button.ts +97 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-item.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-skeleton.ts +33 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-sub-button.ts +41 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-sub-item.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-sub.ts +18 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu.ts +15 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-rail.ts +34 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-separator.ts +17 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-trigger.ts +35 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar-wrapper.ts +24 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar.service.ts +136 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar.stories.ts +177 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar.token.ts +33 -0
- package/src/lib/ui/sidebar/src/lib/hlm-sidebar.ts +139 -0
- package/src/lib/ui/skeleton/src/index.ts +5 -0
- package/src/lib/ui/skeleton/src/lib/hlm-skeleton.ts +14 -0
- package/src/lib/ui/table/src/index.ts +25 -0
- package/src/lib/ui/table/src/lib/hlm-table.stories.ts +70 -0
- package/src/lib/ui/table/src/lib/hlm-table.ts +129 -0
- package/src/lib/ui/textarea/src/index.ts +5 -0
- package/src/lib/ui/textarea/src/lib/hlm-textarea.stories.ts +72 -0
- package/src/lib/ui/textarea/src/lib/hlm-textarea.ts +21 -0
- package/src/lib/ui/tooltip/src/index.ts +5 -0
- package/src/lib/ui/tooltip/src/lib/hlm-tooltip.ts +44 -0
- package/src/lib/ui/utils/src/index.ts +1 -0
- package/src/lib/ui/utils/src/lib/hlm.ts +313 -0
- package/src/public-api.ts +24 -0
- package/src/styles.css +43 -0
- package/tsconfig.json +45 -0
- package/tsconfig.lib.json +11 -0
- package/tsconfig.lib.prod.json +9 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
3
|
+
import { phosphorCaretRight } from '@ng-icons/phosphor-icons/regular';
|
|
4
|
+
import { classes } from '../../../utils/src';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'hlm-dropdown-menu-item-sub-indicator',
|
|
8
|
+
imports: [NgIcon],
|
|
9
|
+
providers: [provideIcons({ phosphorCaretRight })],
|
|
10
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11
|
+
template: ` <ng-icon name="phosphorCaretRight" class="text-base rtl:rotate-180" /> `,
|
|
12
|
+
})
|
|
13
|
+
export class HlmDropdownMenuItemSubIndicator {
|
|
14
|
+
constructor() {
|
|
15
|
+
classes(() => 'ms-auto size-4');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type BooleanInput } from '@angular/cdk/coercion';
|
|
2
|
+
import { CdkMenuItem } from '@angular/cdk/menu';
|
|
3
|
+
import { booleanAttribute, Directive, HOST_TAG_NAME, inject, input } from '@angular/core';
|
|
4
|
+
import { classes } from '../../../utils/src';
|
|
5
|
+
import type { DropdownMenuItemVariantName } from '@surfnet/curve-contracts';
|
|
6
|
+
|
|
7
|
+
@Directive({
|
|
8
|
+
selector: '[hlmDropdownMenuItem],hlm-dropdown-menu-item',
|
|
9
|
+
hostDirectives: [
|
|
10
|
+
{
|
|
11
|
+
directive: CdkMenuItem,
|
|
12
|
+
inputs: ['cdkMenuItemDisabled: disabled'],
|
|
13
|
+
outputs: ['cdkMenuItemTriggered: triggered'],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
host: {
|
|
17
|
+
'data-slot': 'dropdown-menu-item',
|
|
18
|
+
'[attr.disabled]': '_isButton && disabled() ? "" : null',
|
|
19
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
20
|
+
'[attr.data-variant]': 'variant()',
|
|
21
|
+
'[attr.data-inset]': 'inset() ? "" : null',
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
export class HlmDropdownMenuItem {
|
|
25
|
+
protected readonly _isButton = inject(HOST_TAG_NAME) === 'button';
|
|
26
|
+
|
|
27
|
+
public readonly disabled = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
|
|
28
|
+
|
|
29
|
+
public readonly variant = input<DropdownMenuItemVariantName>('default');
|
|
30
|
+
|
|
31
|
+
public readonly inset = input<boolean, BooleanInput>(false, {
|
|
32
|
+
transform: booleanAttribute,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
classes(
|
|
37
|
+
() =>
|
|
38
|
+
"hover:bg-accent focus-visible:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[ng-icon]:!text-destructive [&_ng-icon:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_ng-icon]:pointer-events-none [&_ng-icon]:shrink-0 [&_svg:not([class*='text-'])]:text-base",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type BooleanInput } from '@angular/cdk/coercion';
|
|
2
|
+
import { booleanAttribute, Directive, input } from '@angular/core';
|
|
3
|
+
import { classes } from '../../../utils/src';
|
|
4
|
+
|
|
5
|
+
@Directive({
|
|
6
|
+
selector: '[hlmDropdownMenuLabel],hlm-dropdown-menu-label',
|
|
7
|
+
host: {
|
|
8
|
+
'data-slot': 'dropdown-menu-label',
|
|
9
|
+
'[attr.data-inset]': 'inset() ? "" : null',
|
|
10
|
+
},
|
|
11
|
+
})
|
|
12
|
+
export class HlmDropdownMenuLabel {
|
|
13
|
+
constructor() {
|
|
14
|
+
classes(() => 'block px-2 py-1.5 text-sm font-medium data-[inset]:pl-8');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public readonly inset = input<boolean, BooleanInput>(false, {
|
|
18
|
+
transform: booleanAttribute,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
3
|
+
import { phosphorCircle } from '@ng-icons/phosphor-icons/regular';
|
|
4
|
+
import { classes } from '../../../utils/src';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'hlm-dropdown-menu-radio-indicator',
|
|
8
|
+
imports: [NgIcon],
|
|
9
|
+
providers: [provideIcons({ phosphorCircle })],
|
|
10
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11
|
+
template: ` <ng-icon name="phosphorCircle" class="text-[0.5rem] *:[svg]:fill-current" /> `,
|
|
12
|
+
})
|
|
13
|
+
export class HlmDropdownMenuRadioIndicator {
|
|
14
|
+
constructor() {
|
|
15
|
+
classes(
|
|
16
|
+
() =>
|
|
17
|
+
'pointer-events-none absolute start-2 flex size-3.5 items-center justify-center opacity-0 group-data-[checked]:opacity-100',
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type BooleanInput } from '@angular/cdk/coercion';
|
|
2
|
+
import { CdkMenuItemRadio } from '@angular/cdk/menu';
|
|
3
|
+
import { Directive, booleanAttribute, inject, input } from '@angular/core';
|
|
4
|
+
import { classes } from '../../../utils/src';
|
|
5
|
+
|
|
6
|
+
@Directive({
|
|
7
|
+
selector: '[hlmDropdownMenuRadio]',
|
|
8
|
+
hostDirectives: [
|
|
9
|
+
{
|
|
10
|
+
directive: CdkMenuItemRadio,
|
|
11
|
+
inputs: ['cdkMenuItemDisabled: disabled', 'cdkMenuItemChecked: checked'],
|
|
12
|
+
outputs: ['cdkMenuItemTriggered: triggered'],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
host: {
|
|
16
|
+
'data-slot': 'dropdown-menu-radio-item',
|
|
17
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
18
|
+
'[attr.data-checked]': 'checked() ? "" : null',
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
export class HlmDropdownMenuRadio {
|
|
22
|
+
private readonly _cdkMenuItem = inject(CdkMenuItemRadio);
|
|
23
|
+
public readonly checked = input<boolean, BooleanInput>(this._cdkMenuItem.checked, {
|
|
24
|
+
transform: booleanAttribute,
|
|
25
|
+
});
|
|
26
|
+
public readonly disabled = input<boolean, BooleanInput>(this._cdkMenuItem.disabled, {
|
|
27
|
+
transform: booleanAttribute,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
classes(
|
|
32
|
+
() =>
|
|
33
|
+
'hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground group relative flex w-full cursor-default items-center rounded-sm py-1.5 ps-8 pe-2 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Directive } from '@angular/core';
|
|
2
|
+
import { classes } from '../../../utils/src';
|
|
3
|
+
|
|
4
|
+
@Directive({
|
|
5
|
+
selector: '[hlmDropdownMenuSeparator],hlm-dropdown-menu-separator',
|
|
6
|
+
host: {
|
|
7
|
+
'data-slot': 'dropdown-menu-separator',
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
export class HlmDropdownMenuSeparator {
|
|
11
|
+
constructor() {
|
|
12
|
+
classes(() => 'bg-border -mx-1 my-1 block h-px');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Directive } from '@angular/core';
|
|
2
|
+
import { classes } from '../../../utils/src';
|
|
3
|
+
|
|
4
|
+
@Directive({
|
|
5
|
+
selector: '[hlmDropdownMenuShortcut],hlm-dropdown-menu-shortcut',
|
|
6
|
+
host: {
|
|
7
|
+
'data-slot': 'dropdown-menu-shortcut',
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
export class HlmDropdownMenuShortcut {
|
|
11
|
+
constructor() {
|
|
12
|
+
classes(() => 'text-muted-foreground ml-auto text-xs tracking-widest');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { CdkMenu } from '@angular/cdk/menu';
|
|
2
|
+
import { Directive, inject, signal } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { classes } from '../../../utils/src';
|
|
5
|
+
|
|
6
|
+
@Directive({
|
|
7
|
+
selector: '[hlmDropdownMenuSub],hlm-dropdown-menu-sub',
|
|
8
|
+
hostDirectives: [CdkMenu],
|
|
9
|
+
host: {
|
|
10
|
+
'data-slot': 'dropdown-menu-sub',
|
|
11
|
+
'[attr.data-state]': '_state()',
|
|
12
|
+
'[attr.data-side]': '_side()',
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
export class HlmDropdownMenuSub {
|
|
16
|
+
private readonly _host = inject(CdkMenu);
|
|
17
|
+
|
|
18
|
+
protected readonly _state = signal('open');
|
|
19
|
+
protected readonly _side = signal('top');
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this.setSideWithDarkMagic();
|
|
23
|
+
// this is a best effort, but does not seem to work currently
|
|
24
|
+
// TODO: figure out a way for us to know the host is about to be closed. might not be possible with CDK
|
|
25
|
+
this._host.closed.pipe(takeUntilDestroyed()).subscribe(() => this._state.set('closed'));
|
|
26
|
+
|
|
27
|
+
classes(
|
|
28
|
+
() =>
|
|
29
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-top overflow-hidden rounded-md border p-1 shadow-lg',
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private setSideWithDarkMagic() {
|
|
34
|
+
/**
|
|
35
|
+
* This is an ugly workaround to at least figure out the correct side of where a submenu
|
|
36
|
+
* will appear and set the attribute to the host accordingly
|
|
37
|
+
*
|
|
38
|
+
* First of all we take advantage of the menu stack not being aware of the root
|
|
39
|
+
* object immediately after it is added. This code executes before the root element is added,
|
|
40
|
+
* which means the stack is still empty and the peek method returns undefined.
|
|
41
|
+
*/
|
|
42
|
+
const isRoot = this._host.menuStack.peek() === undefined;
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
// our menu trigger directive leaves the last position used for use immediately after opening
|
|
45
|
+
// we can access it here and determine the correct side.
|
|
46
|
+
// eslint-disable-next-line
|
|
47
|
+
const ps = (this._host as any)._parentTrigger._spartanLastPosition;
|
|
48
|
+
if (!ps) {
|
|
49
|
+
// if we have no last position we default to the most likely option
|
|
50
|
+
// I hate that we have to do this and hope we can revisit soon and improve
|
|
51
|
+
this._side.set(isRoot ? 'top' : 'left');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const side = isRoot ? ps.originY : ps.originX === 'end' ? 'right' : 'left';
|
|
55
|
+
this._side.set(side);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { InjectionToken, type ValueProvider, inject } from '@angular/core';
|
|
2
|
+
import { type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
3
|
+
|
|
4
|
+
export interface HlmDropdownMenuConfig {
|
|
5
|
+
align: MenuAlign;
|
|
6
|
+
side: MenuSide;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const defaultConfig: HlmDropdownMenuConfig = {
|
|
10
|
+
align: 'start',
|
|
11
|
+
side: 'bottom',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const HlmDropdownMenuConfigToken = new InjectionToken<HlmDropdownMenuConfig>(
|
|
15
|
+
'HlmDropdownMenuConfig',
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export function provideHlmDropdownMenuConfig(
|
|
19
|
+
config: Partial<HlmDropdownMenuConfig>,
|
|
20
|
+
): ValueProvider {
|
|
21
|
+
return { provide: HlmDropdownMenuConfigToken, useValue: { ...defaultConfig, ...config } };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function injectHlmDropdownMenuConfig(): HlmDropdownMenuConfig {
|
|
25
|
+
return inject(HlmDropdownMenuConfigToken, { optional: true }) ?? defaultConfig;
|
|
26
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CdkMenuTrigger } from '@angular/cdk/menu';
|
|
2
|
+
import { computed, Directive, effect, inject, input } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { createMenuPosition, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
5
|
+
import { injectHlmDropdownMenuConfig } from './hlm-dropdown-menu-token';
|
|
6
|
+
|
|
7
|
+
@Directive({
|
|
8
|
+
selector: '[hlmDropdownMenuTrigger]',
|
|
9
|
+
hostDirectives: [
|
|
10
|
+
{
|
|
11
|
+
directive: CdkMenuTrigger,
|
|
12
|
+
inputs: [
|
|
13
|
+
'cdkMenuTriggerFor: hlmDropdownMenuTrigger',
|
|
14
|
+
'cdkMenuTriggerData: hlmDropdownMenuTriggerData',
|
|
15
|
+
],
|
|
16
|
+
outputs: ['cdkMenuOpened: hlmDropdownMenuOpened', 'cdkMenuClosed: hlmDropdownMenuClosed'],
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
host: {
|
|
20
|
+
'data-slot': 'dropdown-menu-trigger',
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
export class HlmDropdownMenuTrigger {
|
|
24
|
+
private readonly _cdkTrigger = inject(CdkMenuTrigger, { host: true });
|
|
25
|
+
private readonly _config = injectHlmDropdownMenuConfig();
|
|
26
|
+
|
|
27
|
+
public readonly align = input<MenuAlign>(this._config.align);
|
|
28
|
+
public readonly side = input<MenuSide>(this._config.side);
|
|
29
|
+
|
|
30
|
+
private readonly _menuPosition = computed(() => createMenuPosition(this.align(), this.side()));
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
// once the trigger opens we wait until the next tick and then grab the last position
|
|
34
|
+
// used to position the menu. we store this in our trigger which the brnMenu directive has
|
|
35
|
+
// access to through DI
|
|
36
|
+
this._cdkTrigger.opened.pipe(takeUntilDestroyed()).subscribe(() =>
|
|
37
|
+
setTimeout(
|
|
38
|
+
() =>
|
|
39
|
+
// eslint-disable-next-line
|
|
40
|
+
((this._cdkTrigger as any)._spartanLastPosition = // eslint-disable-next-line
|
|
41
|
+
(this._cdkTrigger as any).overlayRef._positionStrategy._lastPosition),
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
effect(() => {
|
|
46
|
+
this._cdkTrigger.menuPosition = this._menuPosition();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
2
|
+
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
3
|
+
import { phosphorDotsThreeVertical } from '@ng-icons/phosphor-icons/regular';
|
|
4
|
+
import { HlmDropdownMenu, HlmDropdownMenuImports } from '..';
|
|
5
|
+
import { type Meta, moduleMetadata, type StoryObj } from '@storybook/angular';
|
|
6
|
+
import { HlmButton, HlmButtonImports } from '../../../../../public-api';
|
|
7
|
+
import { dropdownMenuContract } from '@surfnet/curve-contracts';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<HlmDropdownMenu> = {
|
|
10
|
+
title: 'Components/Dropdown Menu',
|
|
11
|
+
component: HlmDropdownMenu,
|
|
12
|
+
decorators: [
|
|
13
|
+
moduleMetadata({
|
|
14
|
+
imports: [HlmDropdownMenuImports, HlmButton, NgIcon],
|
|
15
|
+
providers: [provideIcons({ phosphorDotsThreeVertical })],
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
parameters: {
|
|
19
|
+
docs: {
|
|
20
|
+
description: {
|
|
21
|
+
component: dropdownMenuContract.docs.description,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story = StoryObj<HlmDropdownMenu>;
|
|
29
|
+
|
|
30
|
+
/** A menu with a label, grouped items, and a destructive action. */
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
render: () => ({
|
|
33
|
+
template: `
|
|
34
|
+
<div class="w-full flex justify-center items-center">
|
|
35
|
+
<button hlmBtn variant="outline" [hlmDropdownMenuTrigger]="menu">Open menu</button>
|
|
36
|
+
</div>
|
|
37
|
+
<ng-template #menu>
|
|
38
|
+
<hlm-dropdown-menu align="start" class="w-48">
|
|
39
|
+
<hlm-dropdown-menu-group>
|
|
40
|
+
<hlm-dropdown-menu-label>My Account</hlm-dropdown-menu-label>
|
|
41
|
+
<button hlmDropdownMenuItem>
|
|
42
|
+
<span>Profile</span>
|
|
43
|
+
<hlm-dropdown-menu-shortcut>⇧⌘P</hlm-dropdown-menu-shortcut>
|
|
44
|
+
</button>
|
|
45
|
+
|
|
46
|
+
<button hlmDropdownMenuItem>Settings</button>
|
|
47
|
+
</hlm-dropdown-menu-group>
|
|
48
|
+
|
|
49
|
+
<hlm-dropdown-menu-separator />
|
|
50
|
+
|
|
51
|
+
<button hlmDropdownMenuItem variant="destructive">Delete</button>
|
|
52
|
+
</hlm-dropdown-menu>
|
|
53
|
+
</ng-template>`,
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** The "⋯" row-actions pattern from a data table. */
|
|
58
|
+
export const RowActions: Story = {
|
|
59
|
+
render: () => ({
|
|
60
|
+
template: `
|
|
61
|
+
<div class="w-full flex justify-center items-center">
|
|
62
|
+
<button hlmBtn variant="ghost" size="icon-sm" align="end" aria-label="Row actions" [hlmDropdownMenuTrigger]="menu">
|
|
63
|
+
<ng-icon name="phosphorDotsThreeVertical" />
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
<ng-template #menu>
|
|
67
|
+
<hlm-dropdown-menu class="w-40">
|
|
68
|
+
<button hlmDropdownMenuItem>View</button>
|
|
69
|
+
<button hlmDropdownMenuItem>Edit</button>
|
|
70
|
+
<hlm-dropdown-menu-separator />
|
|
71
|
+
<button hlmDropdownMenuItem variant="destructive">Remove</button>
|
|
72
|
+
</hlm-dropdown-menu>
|
|
73
|
+
</ng-template>`,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Checkbox items for toggling options. */
|
|
78
|
+
export const WithCheckboxItems: Story = {
|
|
79
|
+
render: () => ({
|
|
80
|
+
moduleMetadata: {
|
|
81
|
+
imports: [DropdownCheckboxes],
|
|
82
|
+
},
|
|
83
|
+
template: '<stateful-dropdown-story/>',
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
@Component({
|
|
88
|
+
selector: 'stateful-dropdown-story',
|
|
89
|
+
imports: [HlmDropdownMenuImports, HlmButtonImports],
|
|
90
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
91
|
+
template: ` <div class="w-full flex justify-center items-center">
|
|
92
|
+
<button
|
|
93
|
+
hlmBtn
|
|
94
|
+
variant="outline"
|
|
95
|
+
aria-label="With Checkbox Items"
|
|
96
|
+
[hlmDropdownMenuTrigger]="menu"
|
|
97
|
+
>
|
|
98
|
+
Columns
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
<ng-template #menu>
|
|
102
|
+
<hlm-dropdown-menu class="w-44">
|
|
103
|
+
<hlm-dropdown-menu-label>Toggle columns</hlm-dropdown-menu-label>
|
|
104
|
+
<button hlmDropdownMenuCheckbox [checked]="true">
|
|
105
|
+
Name
|
|
106
|
+
<hlm-dropdown-menu-checkbox-indicator />
|
|
107
|
+
</button>
|
|
108
|
+
<button hlmDropdownMenuCheckbox [checked]="true">
|
|
109
|
+
Category
|
|
110
|
+
<hlm-dropdown-menu-checkbox-indicator />
|
|
111
|
+
</button>
|
|
112
|
+
<button
|
|
113
|
+
hlmDropdownMenuCheckbox
|
|
114
|
+
[checked]="isStatusChecked()"
|
|
115
|
+
(triggered)="isStatusChecked.set(!isStatusChecked())"
|
|
116
|
+
>
|
|
117
|
+
Status
|
|
118
|
+
<hlm-dropdown-menu-checkbox-indicator />
|
|
119
|
+
</button>
|
|
120
|
+
</hlm-dropdown-menu>
|
|
121
|
+
</ng-template>`,
|
|
122
|
+
})
|
|
123
|
+
class DropdownCheckboxes {
|
|
124
|
+
public readonly isStatusChecked = signal(false);
|
|
125
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type NumberInput } from '@angular/cdk/coercion';
|
|
2
|
+
import { CdkMenu } from '@angular/cdk/menu';
|
|
3
|
+
import { Directive, inject, input, numberAttribute, signal } from '@angular/core';
|
|
4
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { classes } from '../../../utils/src';
|
|
6
|
+
|
|
7
|
+
@Directive({
|
|
8
|
+
selector: '[hlmDropdownMenu],hlm-dropdown-menu',
|
|
9
|
+
hostDirectives: [CdkMenu],
|
|
10
|
+
host: {
|
|
11
|
+
'data-slot': 'dropdown-menu',
|
|
12
|
+
'[attr.data-state]': '_state()',
|
|
13
|
+
'[attr.data-side]': '_side()',
|
|
14
|
+
'[style.--side-offset]': 'sideOffset()',
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
export class HlmDropdownMenu {
|
|
18
|
+
private readonly _host = inject(CdkMenu);
|
|
19
|
+
|
|
20
|
+
protected readonly _state = signal('open');
|
|
21
|
+
protected readonly _side = signal('top');
|
|
22
|
+
|
|
23
|
+
public readonly sideOffset = input<number, NumberInput>(1, { transform: numberAttribute });
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
classes(
|
|
27
|
+
() =>
|
|
28
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 my-[--spacing(var(--side-offset))] min-w-[8rem] origin-top overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
this.setSideWithDarkMagic();
|
|
32
|
+
// this is a best effort, but does not seem to work currently
|
|
33
|
+
// TODO: figure out a way for us to know the host is about to be closed. might not be possible with CDK
|
|
34
|
+
this._host.closed.pipe(takeUntilDestroyed()).subscribe(() => this._state.set('closed'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private setSideWithDarkMagic() {
|
|
38
|
+
/**
|
|
39
|
+
* This is an ugly workaround to at least figure out the correct side of where a submenu
|
|
40
|
+
* will appear and set the attribute to the host accordingly
|
|
41
|
+
*
|
|
42
|
+
* First of all we take advantage of the menu stack not being aware of the root
|
|
43
|
+
* object immediately after it is added. This code executes before the root element is added,
|
|
44
|
+
* which means the stack is still empty and the peek method returns undefined.
|
|
45
|
+
*/
|
|
46
|
+
const isRoot = this._host.menuStack.peek() === undefined;
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
// our menu trigger directive leaves the last position used for use immediately after opening
|
|
49
|
+
// we can access it here and determine the correct side.
|
|
50
|
+
// eslint-disable-next-line
|
|
51
|
+
const ps = (this._host as any)._parentTrigger._spartanLastPosition;
|
|
52
|
+
if (!ps) {
|
|
53
|
+
// if we have no last position we default to the most likely option
|
|
54
|
+
// I hate that we have to do this and hope we can revisit soon and improve
|
|
55
|
+
this._side.set(isRoot ? 'top' : 'left');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const side = isRoot ? ps.originY : ps.originX === 'end' ? 'right' : 'left';
|
|
59
|
+
this._side.set(side);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { HlmField } from './lib/hlm-field';
|
|
2
|
+
import { HlmFieldContent } from './lib/hlm-field-content';
|
|
3
|
+
import { HlmFieldDescription } from './lib/hlm-field-description';
|
|
4
|
+
import { HlmFieldError } from './lib/hlm-field-error';
|
|
5
|
+
import { HlmFieldGroup } from './lib/hlm-field-group';
|
|
6
|
+
import { HlmFieldLabel } from './lib/hlm-field-label';
|
|
7
|
+
import { HlmFieldLegend } from './lib/hlm-field-legend';
|
|
8
|
+
import { HlmFieldSeparator } from './lib/hlm-field-separator';
|
|
9
|
+
import { HlmFieldSet } from './lib/hlm-field-set';
|
|
10
|
+
import { HlmFieldTitle } from './lib/hlm-field-title';
|
|
11
|
+
|
|
12
|
+
export * from './lib/hlm-field';
|
|
13
|
+
export * from './lib/hlm-field-content';
|
|
14
|
+
export * from './lib/hlm-field-description';
|
|
15
|
+
export * from './lib/hlm-field-error';
|
|
16
|
+
export * from './lib/hlm-field-group';
|
|
17
|
+
export * from './lib/hlm-field-label';
|
|
18
|
+
export * from './lib/hlm-field-legend';
|
|
19
|
+
export * from './lib/hlm-field-separator';
|
|
20
|
+
export * from './lib/hlm-field-set';
|
|
21
|
+
export * from './lib/hlm-field-title';
|
|
22
|
+
|
|
23
|
+
export const HlmFieldImports = [
|
|
24
|
+
HlmField,
|
|
25
|
+
HlmFieldContent,
|
|
26
|
+
HlmFieldDescription,
|
|
27
|
+
HlmFieldError,
|
|
28
|
+
HlmFieldGroup,
|
|
29
|
+
HlmFieldLabel,
|
|
30
|
+
HlmFieldLegend,
|
|
31
|
+
HlmFieldSeparator,
|
|
32
|
+
HlmFieldSet,
|
|
33
|
+
HlmFieldTitle,
|
|
34
|
+
] as const;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Directive } from '@angular/core';
|
|
2
|
+
import { classes } from '../../../utils/src';
|
|
3
|
+
|
|
4
|
+
@Directive({
|
|
5
|
+
selector: '[hlmFieldContent],hlm-field-content',
|
|
6
|
+
host: { 'data-slot': 'field-content' },
|
|
7
|
+
})
|
|
8
|
+
export class HlmFieldContent {
|
|
9
|
+
constructor() {
|
|
10
|
+
classes(() => 'gap-1 group/field-content flex flex-1 flex-col leading-snug');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Directive, effect, EffectRef, inject, input, OnDestroy } from '@angular/core';
|
|
2
|
+
import { BrnFieldA11yService } from '@spartan-ng/brain/field';
|
|
3
|
+
import { classes } from '../../../utils/src';
|
|
4
|
+
|
|
5
|
+
@Directive({
|
|
6
|
+
selector: '[hlmFieldDescription],hlm-field-description',
|
|
7
|
+
host: {
|
|
8
|
+
'data-slot': 'field-description',
|
|
9
|
+
'[attr.id]': 'id()',
|
|
10
|
+
},
|
|
11
|
+
})
|
|
12
|
+
export class HlmFieldDescription implements OnDestroy {
|
|
13
|
+
private static _id = 0;
|
|
14
|
+
|
|
15
|
+
private readonly _a11y = inject(BrnFieldA11yService, { optional: true, host: true });
|
|
16
|
+
|
|
17
|
+
public readonly id = input<string>(`hlm-field-description-${HlmFieldDescription._id++}`);
|
|
18
|
+
|
|
19
|
+
private _registeredId?: string;
|
|
20
|
+
|
|
21
|
+
private readonly _cleanup: EffectRef | null = this._a11y
|
|
22
|
+
? effect(() => {
|
|
23
|
+
const a11y = this._a11y;
|
|
24
|
+
if (!a11y) return;
|
|
25
|
+
|
|
26
|
+
const id = this.id();
|
|
27
|
+
if (this._registeredId && this._registeredId !== id) {
|
|
28
|
+
a11y.unregisterDescription(this._registeredId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (this._registeredId !== id) {
|
|
32
|
+
a11y.registerDescription(id);
|
|
33
|
+
this._registeredId = id;
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
: null;
|
|
37
|
+
|
|
38
|
+
constructor() {
|
|
39
|
+
classes(() => [
|
|
40
|
+
'text-muted-foreground text-start text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-horizontal/field:text-balance',
|
|
41
|
+
'last:mt-0 nth-last-2:-mt-1',
|
|
42
|
+
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
ngOnDestroy() {
|
|
47
|
+
this._cleanup?.destroy();
|
|
48
|
+
|
|
49
|
+
if (this._registeredId) {
|
|
50
|
+
this._a11y?.unregisterDescription(this._registeredId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|