diff --git a/alfa-client/libs/design-system/src/lib/checkbox/checbox.stories.ts b/alfa-client/libs/design-system/src/lib/checkbox/checbox.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b3954527bc86c75615e36839194754fce3466ef --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/checkbox/checbox.stories.ts @@ -0,0 +1,67 @@ +import { FormControl } from '@angular/forms'; +import { argsToTemplate, moduleMetadata, type Meta, type StoryObj } from '@storybook/angular'; +import { CheckboxComponent } from './checkbox.component'; + +const formControlWithError = new FormControl(false); +formControlWithError.setErrors({ error: 404 }); + +const meta: Meta<CheckboxComponent> = { + title: 'Checkbox', + component: CheckboxComponent, + decorators: [ + moduleMetadata({ + imports: [CheckboxComponent], + }), + ], + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<CheckboxComponent>; + +export const Default: Story = { + args: { + value: 'Checkbox value', + label: 'Basic checkbox', + inputId: '1', + disabled: false, + }, + argTypes: { + label: { description: 'Checkbox label' }, + disabled: { description: 'Disabled state of checkbox' }, + inputId: { description: 'Id of checkbox input' }, + value: { description: 'Value of checkbox' }, + fieldControl: { + description: 'Form control object', + table: { type: { summary: 'FormControl' } }, + }, + }, +}; + +export const Error: Story = { + args: { + label: 'Checkbox with error state', + inputId: '2', + fieldControl: formControlWithError, + }, +}; + +export const Disabled: Story = { + args: { + label: 'Disabled checkbox', + inputId: '3', + disabled: true, + }, +}; + +export const MultiLineLabel: Story = { + args: { + label: 'Very long label that should break', + inputId: '4', + }, + render: (args) => ({ + props: args, + template: `<div class="w-40"><ods-checkbox ${argsToTemplate(args)}/></div>`, + }), +}; diff --git a/alfa-client/libs/design-system/src/lib/checkbox/checkbox.component.spec.ts b/alfa-client/libs/design-system/src/lib/checkbox/checkbox.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c25c5efdcce199e3c83dfd6adfceed567f2ea5d3 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/checkbox/checkbox.component.spec.ts @@ -0,0 +1,45 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl } from '@angular/forms'; +import { CheckboxComponent } from './checkbox.component'; + +describe('CheckboxComponent', () => { + let component: CheckboxComponent; + let fixture: ComponentFixture<CheckboxComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CheckboxComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CheckboxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('component', () => { + describe('hasError', () => { + it('should return true', () => { + const formControlWithError = new FormControl(false); + formControlWithError.setErrors({ 1: 'error' }); + component.fieldControl = formControlWithError; + + const result: boolean = component.hasError(); + + expect(result).toBe(true); + }); + + it('should return false', () => { + const formControl = new FormControl(false); + component.fieldControl = formControl; + + const result: boolean = component.hasError(); + + expect(result).toBe(false); + }); + }); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/checkbox/checkbox.component.ts b/alfa-client/libs/design-system/src/lib/checkbox/checkbox.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4e2067da578357fe55549d6b912c8a5ed49d1444 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/checkbox/checkbox.component.ts @@ -0,0 +1,52 @@ +import { isNotEmpty } from '@alfa-client/tech-shared'; +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; + +@Component({ + selector: 'ods-checkbox', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + template: ` + <div class="flex items-start gap-3 text-start"> + <input + type="checkbox" + class="disabled:border-disabled-dark disabled:bg-disabled peer relative box-border size-5 + shrink-0 appearance-none rounded-sm border bg-whitetext outline-2 outline-offset-4 + hover:border-2 focus-visible:border-background-200 disabled:hover:border" + [ngClass]=" + hasError() ? + 'border-error hover:border-error focus-visible:outline-error' + : 'border-primary hover:border-primary-hover focus-visible:outline-focus' + " + [value]="value" + [checked]="fieldControl.value" + [attr.id]="inputId" + [disabled]="disabled" + /> + <label class="leading-5 text-text" [attr.for]="inputId">{{ label }}</label> + <svg + viewBox="0 0 12 9" + xmlns="http://www.w3.org/2000/svg" + class="pointer-events-none absolute hidden size-5 p-1 outline-none peer-checked:block" + [ngClass]="hasError() ? 'fill-error' : 'fill-primary focus-visible:fill-focus'" + > + <path + d="M3.81353 7.10067L0.968732 4.30201L0 5.24832L3.81353 + 9L12 0.946309L11.0381 0L3.81353 7.10067Z" + /> + </svg> + </div> + `, +}) +export class CheckboxComponent { + @Input() fieldControl: FormControl = new FormControl(false); + @Input() value: string; + @Input() inputId: string; + @Input() label: string; + @Input() disabled: boolean = false; + + hasError(): boolean { + return isNotEmpty(this.fieldControl.errors); + } +} diff --git a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js index cb8ed07e1d8c25e88620f6a60df143c9e7da8dfb..54e07db85e25d361483c809da24eadf05af58ca4 100644 --- a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js +++ b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js @@ -119,6 +119,10 @@ module.exports = { warning: 'hsl(var(--warning))', error: 'hsl(var(--color-error))', focus: 'hsl(var(--color-focus))', + disabled: { + dark: 'hsl(208, 12%, 65%)', + DEFAULT: 'hsl(206, 14%, 95%)', + }, }, backgroundColor: { greybackdrop: 'rgb(229, 229, 229, 0.95)',