Преглед на файлове

chore(admin-ui): Add Spartan components

David Höck преди 11 месеца
родител
ревизия
e435e7e83f
променени са 24 файла, в които са добавени 647 реда и са изтрити 0 реда
  1. 32 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/index.ts
  2. 24 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-ellipsis.component.ts
  3. 16 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-item.directive.ts
  4. 36 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-link.directive.ts
  5. 18 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-list.directive.ts
  6. 19 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-page.directive.ts
  7. 31 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-separator.component.ts
  8. 19 0
      packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb.directive.ts
  9. 71 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/index.ts
  10. 22 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-bar-item.directive.ts
  11. 20 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-bar.component.ts
  12. 15 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-group.component.ts
  13. 29 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-check.component.ts
  14. 28 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-checkbox.directive.ts
  15. 17 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-icon.directive.ts
  16. 29 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-radio.component.ts
  17. 28 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-radio.directive.ts
  18. 23 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-sub-indicator.component.ts
  19. 40 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item.directive.ts
  20. 26 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-label.component.ts
  21. 16 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-separator.component.ts
  22. 20 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-shortcut.component.ts
  23. 43 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu.component.ts
  24. 25 0
      packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-sub-menu.component.ts

+ 32 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/index.ts

@@ -0,0 +1,32 @@
+import { NgModule } from '@angular/core';
+import { HlmBreadcrumbEllipsisComponent } from './lib/breadcrumb-ellipsis.component';
+import { HlmBreadcrumbItemDirective } from './lib/breadcrumb-item.directive';
+import { HlmBreadcrumbLinkDirective } from './lib/breadcrumb-link.directive';
+import { HlmBreadcrumbListDirective } from './lib/breadcrumb-list.directive';
+import { HlmBreadcrumbPageDirective } from './lib/breadcrumb-page.directive';
+import { HlmBreadcrumbSeparatorComponent } from './lib/breadcrumb-separator.component';
+import { HlmBreadcrumbDirective } from './lib/breadcrumb.directive';
+
+export * from './lib/breadcrumb-ellipsis.component';
+export * from './lib/breadcrumb-item.directive';
+export * from './lib/breadcrumb-link.directive';
+export * from './lib/breadcrumb-list.directive';
+export * from './lib/breadcrumb-page.directive';
+export * from './lib/breadcrumb-separator.component';
+export * from './lib/breadcrumb.directive';
+
+export const HlmBreadCrumbImports = [
+	HlmBreadcrumbDirective,
+	HlmBreadcrumbEllipsisComponent,
+	HlmBreadcrumbSeparatorComponent,
+	HlmBreadcrumbItemDirective,
+	HlmBreadcrumbLinkDirective,
+	HlmBreadcrumbPageDirective,
+	HlmBreadcrumbListDirective,
+] as const;
+
+@NgModule({
+	imports: [...HlmBreadCrumbImports],
+	exports: [...HlmBreadCrumbImports],
+})
+export class HlmBreadCrumbModule {}

+ 24 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-ellipsis.component.ts

@@ -0,0 +1,24 @@
+import { Component, computed, input } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { lucideEllipsis } from '@ng-icons/lucide';
+import { hlm } from '@spartan-ng/brain/core';
+import { HlmIconDirective } from '@spartan-ng/ui-icon-helm';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-breadcrumb-ellipsis',
+	standalone: true,
+	imports: [NgIcon, HlmIconDirective],
+	providers: [provideIcons({ lucideEllipsis })],
+	template: `
+		<span role="presentation" aria-hidden="true" [class]="_computedClass()">
+			<ng-icon hlm size="sm" name="lucideEllipsis" />
+			<span class="sr-only">More</span>
+		</span>
+	`,
+})
+export class HlmBreadcrumbEllipsisComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+
+	protected readonly _computedClass = computed(() => hlm('flex h-9 w-9 items-center justify-center', this.userClass()));
+}

+ 16 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-item.directive.ts

@@ -0,0 +1,16 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmBreadcrumbItem]',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmBreadcrumbItemDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+
+	protected readonly _computedClass = computed(() => hlm('inline-flex items-center gap-1.5', this.userClass()));
+}

+ 36 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-link.directive.ts

@@ -0,0 +1,36 @@
+import { Directive, computed, input } from '@angular/core';
+import { RouterLink } from '@angular/router';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmBreadcrumbLink]',
+	standalone: true,
+	hostDirectives: [
+		{
+			directive: RouterLink,
+			inputs: [
+				'target',
+				'queryParams',
+				'fragment',
+				'queryParamsHandling',
+				'state',
+				'info',
+				'relativeTo',
+				'preserveFragment',
+				'skipLocationChange',
+				'replaceUrl',
+				'routerLink: link',
+			],
+		},
+	],
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmBreadcrumbLinkDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	public readonly link = input<RouterLink['routerLink']>();
+
+	protected readonly _computedClass = computed(() => hlm('transition-colors hover:text-foreground', this.userClass()));
+}

+ 18 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-list.directive.ts

@@ -0,0 +1,18 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmBreadcrumbList]',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmBreadcrumbListDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+
+	protected readonly _computedClass = computed(() =>
+		hlm('flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5', this.userClass()),
+	);
+}

+ 19 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-page.directive.ts

@@ -0,0 +1,19 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmBreadcrumbPage]',
+	standalone: true,
+	host: {
+		role: 'link',
+		'[class]': '_computedClass()',
+		'[attr.aria-disabled]': 'disabled',
+		'[attr.aria-current]': 'page',
+	},
+})
+export class HlmBreadcrumbPageDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+
+	protected readonly _computedClass = computed(() => hlm('font-normal text-foreground', this.userClass()));
+}

+ 31 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb-separator.component.ts

@@ -0,0 +1,31 @@
+import { Component, computed, input } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { lucideChevronRight } from '@ng-icons/lucide';
+import { hlm } from '@spartan-ng/brain/core';
+import { HlmIconDirective } from '@spartan-ng/ui-icon-helm';
+import type { ClassValue } from 'clsx';
+
+@Component({
+    // eslint-disable-next-line @angular-eslint/component-selector
+    selector: '[hlmBreadcrumbSeparator]',
+    standalone: true,
+    imports: [NgIcon, HlmIconDirective],
+    providers: [provideIcons({ lucideChevronRight })],
+    host: {
+        role: 'presentation',
+        '[class]': '_computedClass()',
+        '[attr.aria-hidden]': 'true',
+    },
+    template: `
+        <ng-content>
+            <ng-icon size="sm" hlm name="lucideChevronRight" />
+        </ng-content>
+    `,
+})
+export class HlmBreadcrumbSeparatorComponent {
+    public readonly userClass = input<ClassValue>('', { alias: 'class' });
+
+    protected readonly _computedClass = computed(() =>
+        hlm('[&>hlm-icon]:w-3.5 flex items-center [&>hlm-icon]:h-3.5 ', this.userClass()),
+    );
+}

+ 19 - 0
packages/admin-ui/src/lib/ui/ui-breadcrumb-helm/src/lib/breadcrumb.directive.ts

@@ -0,0 +1,19 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmBreadcrumb]',
+	standalone: true,
+	host: {
+		role: 'navigation',
+		'[class]': '_computedClass()',
+		'[attr.aria-label]': 'ariaLabel()',
+	},
+})
+export class HlmBreadcrumbDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	public readonly ariaLabel = input<string>('breadcrumb', { alias: 'aria-label' });
+
+	protected readonly _computedClass = computed(() => hlm(this.userClass()));
+}

+ 71 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/index.ts

@@ -0,0 +1,71 @@
+import { NgModule } from '@angular/core';
+
+import { HlmMenuBarItemDirective } from './lib/hlm-menu-bar-item.directive';
+import { HlmMenuBarComponent } from './lib/hlm-menu-bar.component';
+import { HlmMenuGroupComponent } from './lib/hlm-menu-group.component';
+import { HlmMenuItemCheckComponent } from './lib/hlm-menu-item-check.component';
+import { HlmMenuItemCheckboxDirective } from './lib/hlm-menu-item-checkbox.directive';
+import { HlmMenuItemIconDirective } from './lib/hlm-menu-item-icon.directive';
+import { HlmMenuItemRadioComponent } from './lib/hlm-menu-item-radio.component';
+import { HlmMenuItemRadioDirective } from './lib/hlm-menu-item-radio.directive';
+import { HlmMenuItemSubIndicatorComponent } from './lib/hlm-menu-item-sub-indicator.component';
+import { HlmMenuItemDirective } from './lib/hlm-menu-item.directive';
+import { HlmMenuLabelComponent } from './lib/hlm-menu-label.component';
+import { HlmMenuSeparatorComponent } from './lib/hlm-menu-separator.component';
+import { HlmMenuShortcutComponent } from './lib/hlm-menu-shortcut.component';
+import { HlmMenuComponent } from './lib/hlm-menu.component';
+import { HlmSubMenuComponent } from './lib/hlm-sub-menu.component';
+
+export * from './lib/hlm-menu-bar-item.directive';
+export * from './lib/hlm-menu-bar.component';
+export * from './lib/hlm-menu-group.component';
+export * from './lib/hlm-menu-item-check.component';
+export * from './lib/hlm-menu-item-checkbox.directive';
+export * from './lib/hlm-menu-item-icon.directive';
+export * from './lib/hlm-menu-item-radio.component';
+export * from './lib/hlm-menu-item-radio.directive';
+export * from './lib/hlm-menu-item-sub-indicator.component';
+export * from './lib/hlm-menu-item.directive';
+export * from './lib/hlm-menu-label.component';
+export * from './lib/hlm-menu-separator.component';
+export * from './lib/hlm-menu-shortcut.component';
+export * from './lib/hlm-menu.component';
+export * from './lib/hlm-sub-menu.component';
+
+export const HlmMenuItemImports = [
+	HlmMenuItemDirective,
+	HlmMenuItemIconDirective,
+	HlmMenuGroupComponent,
+	HlmMenuItemSubIndicatorComponent,
+	HlmMenuItemRadioComponent,
+	HlmMenuItemCheckComponent,
+	HlmMenuShortcutComponent,
+	HlmMenuItemCheckboxDirective,
+	HlmMenuItemRadioDirective,
+];
+export const HlmMenuStructureImports = [HlmMenuLabelComponent, HlmMenuSeparatorComponent] as const;
+export const HlmMenuImports = [
+	...HlmMenuItemImports,
+	...HlmMenuStructureImports,
+	HlmMenuComponent,
+	HlmSubMenuComponent,
+] as const;
+export const HlmMenuBarImports = [...HlmMenuImports, HlmMenuBarComponent, HlmMenuBarItemDirective] as const;
+
+@NgModule({
+	imports: [...HlmMenuItemImports],
+	exports: [...HlmMenuItemImports],
+})
+export class HlmMenuItemModule {}
+
+@NgModule({
+	imports: [...HlmMenuImports],
+	exports: [...HlmMenuImports],
+})
+export class HlmMenuModule {}
+
+@NgModule({
+	imports: [...HlmMenuBarImports],
+	exports: [...HlmMenuBarImports],
+})
+export class HlmMenuBarModule {}

+ 22 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-bar-item.directive.ts

@@ -0,0 +1,22 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { BrnMenuItemDirective } from '@spartan-ng/brain/menu';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmMenuBarItem]',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+	hostDirectives: [BrnMenuItemDirective],
+})
+export class HlmMenuBarItemDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm(
+			'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-accent-foreground',
+			this.userClass(),
+		),
+	);
+}

+ 20 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-bar.component.ts

@@ -0,0 +1,20 @@
+import { Component, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { BrnMenuBarDirective } from '@spartan-ng/brain/menu';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-menu-bar',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+	hostDirectives: [BrnMenuBarDirective],
+	template: '<ng-content/>',
+})
+export class HlmMenuBarComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm('border-border flex h-10 items-center space-x-1 rounded-md border bg-background p-1', this.userClass()),
+	);
+}

+ 15 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-group.component.ts

@@ -0,0 +1,15 @@
+import { Component } from '@angular/core';
+import { BrnMenuGroupDirective } from '@spartan-ng/brain/menu';
+
+@Component({
+	selector: 'hlm-menu-group',
+	standalone: true,
+	host: {
+		class: 'block',
+	},
+	hostDirectives: [BrnMenuGroupDirective],
+	template: `
+		<ng-content />
+	`,
+})
+export class HlmMenuGroupComponent {}

+ 29 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-check.component.ts

@@ -0,0 +1,29 @@
+import { Component, computed, input } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { lucideCheck } from '@ng-icons/lucide';
+import { hlm } from '@spartan-ng/brain/core';
+import { HlmIconDirective } from '@spartan-ng/ui-icon-helm';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-menu-item-check',
+	standalone: true,
+	providers: [provideIcons({ lucideCheck })],
+	imports: [NgIcon, HlmIconDirective],
+	template: `
+		<!-- Using 1rem for size to mimick h-4 w-4 -->
+		<ng-icon hlm size="1rem" name="lucideCheck" />
+	`,
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmMenuItemCheckComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm(
+			'group-[.checked]:opacity-100 opacity-0 absolute left-2 flex h-3.5 w-3.5 items-center justify-center',
+			this.userClass(),
+		),
+	);
+}

+ 28 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-checkbox.directive.ts

@@ -0,0 +1,28 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { BrnMenuItemCheckboxDirective } from '@spartan-ng/brain/menu';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmMenuItemCheckbox]',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+	hostDirectives: [
+		{
+			directive: BrnMenuItemCheckboxDirective,
+			inputs: ['disabled: disabled', 'checked: checked'],
+			outputs: ['triggered: triggered'],
+		},
+	],
+})
+export class HlmMenuItemCheckboxDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm(
+			'group w-full relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground disabled:pointer-events-none disabled:opacity-50',
+			this.userClass(),
+		),
+	);
+}

+ 17 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-icon.directive.ts

@@ -0,0 +1,17 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { provideHlmIconConfig } from '@spartan-ng/ui-icon-helm';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmMenuIcon]',
+	standalone: true,
+	providers: [provideHlmIconConfig({ size: 'sm' })],
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmMenuItemIconDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() => hlm('mr-2', this.userClass()));
+}

+ 29 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-radio.component.ts

@@ -0,0 +1,29 @@
+import { Component, computed, input } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { lucideCircle } from '@ng-icons/lucide';
+import { hlm } from '@spartan-ng/brain/core';
+import { HlmIconDirective } from '@spartan-ng/ui-icon-helm';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-menu-item-radio',
+	standalone: true,
+	providers: [provideIcons({ lucideCircle })],
+	imports: [NgIcon, HlmIconDirective],
+	template: `
+		<!-- Using 0.5rem for size to mimick h-2 w-2 -->
+		<ng-icon hlm size="0.5rem" class="*:*:fill-current" name="lucideCircle" />
+	`,
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmMenuItemRadioComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm(
+			'group-[.checked]:opacity-100 opacity-0 absolute left-2 flex h-3.5 w-3.5 items-center justify-center',
+			this.userClass(),
+		),
+	);
+}

+ 28 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-radio.directive.ts

@@ -0,0 +1,28 @@
+import { Directive, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { BrnMenuItemRadioDirective } from '@spartan-ng/brain/menu';
+import type { ClassValue } from 'clsx';
+
+@Directive({
+	selector: '[hlmMenuItemRadio]',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+	hostDirectives: [
+		{
+			directive: BrnMenuItemRadioDirective,
+			inputs: ['disabled: disabled', 'checked: checked'],
+			outputs: ['triggered: triggered'],
+		},
+	],
+})
+export class HlmMenuItemRadioDirective {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm(
+			'group w-full relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground disabled:pointer-events-none disabled:opacity-50',
+			this.userClass(),
+		),
+	);
+}

+ 23 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item-sub-indicator.component.ts

@@ -0,0 +1,23 @@
+import { Component, computed, input } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { lucideChevronRight } from '@ng-icons/lucide';
+import { hlm } from '@spartan-ng/brain/core';
+import { HlmIconDirective } from '@spartan-ng/ui-icon-helm';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-menu-item-sub-indicator',
+	standalone: true,
+	providers: [provideIcons({ lucideChevronRight })],
+	imports: [NgIcon, HlmIconDirective],
+	template: `
+		<ng-icon hlm size="none" class="h-full w-full" name="lucideChevronRight" />
+	`,
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmMenuItemSubIndicatorComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() => hlm('inline-block ml-auto h-4 w-4', this.userClass()));
+}

+ 40 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-item.directive.ts

@@ -0,0 +1,40 @@
+import { Directive, Input, booleanAttribute, computed, input, signal } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { BrnMenuItemDirective } from '@spartan-ng/brain/menu';
+import { type VariantProps, cva } from 'class-variance-authority';
+import type { ClassValue } from 'clsx';
+
+export const hlmMenuItemVariants = cva(
+	'group w-full relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground disabled:pointer-events-none disabled:opacity-50',
+	{
+		variants: { inset: { true: 'pl-8', false: '' } },
+		defaultVariants: { inset: false },
+	},
+);
+export type HlmMenuItemVariants = VariantProps<typeof hlmMenuItemVariants>;
+
+@Directive({
+	selector: '[hlmMenuItem]',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+	hostDirectives: [
+		{
+			directive: BrnMenuItemDirective,
+			inputs: ['disabled: disabled'],
+			outputs: ['triggered: triggered'],
+		},
+	],
+})
+export class HlmMenuItemDirective {
+	private readonly _inset = signal<boolean>(false);
+
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() => hlm(hlmMenuItemVariants({ inset: this._inset() }), this.userClass()));
+
+	@Input({ transform: booleanAttribute })
+	public set inset(value: boolean) {
+		this._inset.set(value);
+	}
+}

+ 26 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-label.component.ts

@@ -0,0 +1,26 @@
+import { Component, Input, booleanAttribute, computed, input, signal } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-menu-label',
+	standalone: true,
+	template: `
+		<ng-content />
+	`,
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmMenuLabelComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm('block px-2 py-1.5 text-sm font-semibold', this._inset() && 'pl-8', this.userClass()),
+	);
+
+	private readonly _inset = signal<ClassValue>(false);
+	@Input({ transform: booleanAttribute })
+	public set inset(value: boolean) {
+		this._inset.set(value);
+	}
+}

+ 16 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-separator.component.ts

@@ -0,0 +1,16 @@
+import { Component, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-menu-separator',
+	standalone: true,
+	template: '',
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmMenuSeparatorComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() => hlm('block -mx-1 my-1 h-px bg-muted', this.userClass()));
+}

+ 20 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu-shortcut.component.ts

@@ -0,0 +1,20 @@
+import { Component, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-menu-shortcut',
+	standalone: true,
+	template: `
+		<ng-content />
+	`,
+	host: {
+		'[class]': '_computedClass()',
+	},
+})
+export class HlmMenuShortcutComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm('ml-auto font-light text-xs tracking-widest opacity-60', this.userClass()),
+	);
+}

+ 43 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-menu.component.ts

@@ -0,0 +1,43 @@
+import { Component, Input, computed, input, signal } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { BrnMenuDirective } from '@spartan-ng/brain/menu';
+import { type VariantProps, cva } from 'class-variance-authority';
+import type { ClassValue } from 'clsx';
+
+export const menuVariants = cva(
+	'block border-border min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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',
+	{
+		variants: {
+			variant: {
+				default: 'my-0.5',
+				menubar: 'my-2',
+			},
+		},
+		defaultVariants: {
+			variant: 'default',
+		},
+	},
+);
+type MenuVariants = VariantProps<typeof menuVariants>;
+
+@Component({
+	selector: 'hlm-menu',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+	hostDirectives: [BrnMenuDirective],
+	template: `
+		<ng-content />
+	`,
+})
+export class HlmMenuComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() => hlm(menuVariants({ variant: this._variant() }), this.userClass()));
+
+	private readonly _variant = signal<MenuVariants['variant']>('default');
+	@Input()
+	public set variant(value: MenuVariants['variant']) {
+		this._variant.set(value);
+	}
+}

+ 25 - 0
packages/admin-ui/src/lib/ui/ui-menu-helm/src/lib/hlm-sub-menu.component.ts

@@ -0,0 +1,25 @@
+import { Component, computed, input } from '@angular/core';
+import { hlm } from '@spartan-ng/brain/core';
+import { BrnMenuDirective } from '@spartan-ng/brain/menu';
+import type { ClassValue } from 'clsx';
+
+@Component({
+	selector: 'hlm-sub-menu',
+	standalone: true,
+	host: {
+		'[class]': '_computedClass()',
+	},
+	hostDirectives: [BrnMenuDirective],
+	template: `
+		<ng-content />
+	`,
+})
+export class HlmSubMenuComponent {
+	public readonly userClass = input<ClassValue>('', { alias: 'class' });
+	protected _computedClass = computed(() =>
+		hlm(
+			'border-border min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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',
+			this.userClass(),
+		),
+	);
+}