From a2f3d210a94a2eeb04f8f6a3ab4fcafe0ad6f3b1 Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Fri, 6 Sep 2024 16:23:58 +0200
Subject: [PATCH] OZG-6237 Add heading component

---
 alfa-client/libs/design-system/src/index.ts   |  1 +
 .../src/lib/heading/heading.component.spec.ts | 75 +++++++++++++++++++
 .../src/lib/heading/heading.component.ts      | 40 ++++++++++
 .../src/lib/heading/heading.stories.ts        | 35 +++++++++
 4 files changed, 151 insertions(+)
 create mode 100644 alfa-client/libs/design-system/src/lib/heading/heading.component.spec.ts
 create mode 100644 alfa-client/libs/design-system/src/lib/heading/heading.component.ts
 create mode 100644 alfa-client/libs/design-system/src/lib/heading/heading.stories.ts

diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts
index 8adc00f2d8..79c0844756 100644
--- a/alfa-client/libs/design-system/src/index.ts
+++ b/alfa-client/libs/design-system/src/index.ts
@@ -10,6 +10,7 @@ export * from './lib/form/file-upload-button/file-upload-button.component';
 export * from './lib/form/radio-button-card/radio-button-card.component';
 export * from './lib/form/text-input/text-input.component';
 export * from './lib/form/textarea/textarea.component';
+export * from './lib/heading/heading.component';
 export * from './lib/icons/admin-logo-icon/admin-logo-icon.component';
 export * from './lib/icons/attachment-icon/attachment-icon.component';
 export * from './lib/icons/bescheid-generate-icon/bescheid-generate-icon.component';
diff --git a/alfa-client/libs/design-system/src/lib/heading/heading.component.spec.ts b/alfa-client/libs/design-system/src/lib/heading/heading.component.spec.ts
new file mode 100644
index 0000000000..50a934b9fe
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/heading/heading.component.spec.ts
@@ -0,0 +1,75 @@
+import { getElementFromFixture } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
+import { HeadingComponent } from './heading.component';
+
+describe('HeadingComponent', () => {
+  let component: HeadingComponent;
+  let fixture: ComponentFixture<HeadingComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [HeadingComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(HeadingComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('input', () => {
+    describe('text', () => {
+      it('should set text as inner HTML', () => {
+        component.text = 'test';
+        fixture.detectChanges();
+
+        const heading: HTMLElement = getElementFromFixture(
+          fixture,
+          getDataTestIdOf('heading-test'),
+        );
+
+        expect(heading.innerHTML).toContain('test');
+      });
+    });
+
+    describe('level', () => {
+      it('should set aria level', () => {
+        component.level = '2';
+        component.text = 'test';
+        fixture.detectChanges();
+
+        const heading: HTMLElement = getElementFromFixture(
+          fixture,
+          getDataTestIdOf('heading-test'),
+        );
+
+        expect(heading.getAttribute('aria-level')).toBe('2');
+      });
+    });
+
+    describe('class', () => {
+      it('should get classes for level', () => {
+        component.headingVariants = jest.fn();
+        component.level = '2';
+
+        fixture.detectChanges();
+
+        expect(component.headingVariants).toHaveBeenCalledWith({ level: '2' });
+      });
+
+      it('should merge classes', () => {
+        component.twMerge = jest.fn();
+        component.headingVariants = jest.fn().mockReturnValue('test-class-2');
+        component.class = 'test-class-1';
+
+        fixture.detectChanges();
+
+        expect(component.twMerge).toHaveBeenCalledWith('test-class-1', 'test-class-2');
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/design-system/src/lib/heading/heading.component.ts b/alfa-client/libs/design-system/src/lib/heading/heading.component.ts
new file mode 100644
index 0000000000..d958e24a13
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/heading/heading.component.ts
@@ -0,0 +1,40 @@
+import { TechSharedModule } from '@alfa-client/tech-shared';
+import { NgClass } from '@angular/common';
+import { Component, Input } from '@angular/core';
+import { VariantProps, cva } from 'class-variance-authority';
+import { twMerge } from 'tailwind-merge';
+
+export const headingVariants = cva('font-medium', {
+  variants: {
+    level: {
+      '1': 'text-3xl pb-6',
+      '2': 'text-2xl py-4',
+    },
+  },
+  defaultVariants: {
+    level: '1',
+  },
+});
+type HeadingVariants = VariantProps<typeof headingVariants>;
+
+@Component({
+  selector: 'ods-heading',
+  standalone: true,
+  imports: [NgClass, TechSharedModule],
+  template: `<div
+    role="heading"
+    [attr.aria-level]="level"
+    [ngClass]="twMerge(class, headingVariants({ level }))"
+    [attr.data-test-id]="'heading-' + text | convertForDataTest"
+  >
+    {{ text }}
+  </div>`,
+})
+export class HeadingComponent {
+  @Input({ required: true }) text!: string;
+  @Input() level: HeadingVariants['level'] = '1';
+  @Input() class: string = '';
+
+  headingVariants = headingVariants;
+  twMerge = twMerge;
+}
diff --git a/alfa-client/libs/design-system/src/lib/heading/heading.stories.ts b/alfa-client/libs/design-system/src/lib/heading/heading.stories.ts
new file mode 100644
index 0000000000..f38efcb047
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/heading/heading.stories.ts
@@ -0,0 +1,35 @@
+import { moduleMetadata, type Meta, type StoryObj } from '@storybook/angular';
+import { HeadingComponent } from './heading.component';
+
+const meta: Meta<HeadingComponent> = {
+  title: 'Heading',
+  component: HeadingComponent,
+  excludeStories: /.*Data$/,
+  tags: ['autodocs'],
+  decorators: [
+    moduleMetadata({
+      imports: [HeadingComponent],
+    }),
+  ],
+};
+
+export default meta;
+type Story = StoryObj<HeadingComponent>;
+
+export const Default: Story = {
+  args: {
+    level: '1',
+    text: 'This is awesome heading!',
+    class: '',
+  },
+  argTypes: {
+    level: {
+      description: 'Level of heading element',
+      control: 'select',
+      options: ['1', '2'],
+      table: { defaultValue: { summary: '1' } },
+    },
+    text: { description: 'Heading text' },
+    class: { description: 'Style class for element' },
+  },
+};
-- 
GitLab