npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@favian/headwind-ui

v18.0.0

Published

Headless UI for Angular - Styleless Angular components to integrate with Tailwind CSS

Downloads

13

Readme

Headwind UI

To see documentation with live examples, visit Headwind UI Homepage.

Getting Started

Headwind UI is a styleless Angular components library. Designed to integrate with Tailwind CSS.

To get started, install Headwind UI via npm.

npm install @favian/headwind-ui

Some components of Headwind UI have a tabindex attribute for accessibility. Set outline to 0 to disable the Browser's default focus effect.

@layer components {
  [class*="headwind-"] {
    @apply outline-0;
  }
}

Accordion

This is a component that can hide or show content using a button.

import { Component } from '@angular/core';
import {
  HeadwindAccordionButtonComponent,
  HeadwindAccordionComponent,
  HeadwindAccordionContentDirective,
} from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-basic-accordion-example',
  standalone: true,
  imports: [HeadwindAccordionComponent, HeadwindAccordionButtonComponent, HeadwindAccordionContentDirective],
  template: `
    <div class="w-full max-w-[400px] space-y-2 rounded-2xl bg-white p-8 shadow">
      @for (person of people; track person.name) {
        <headwind-accordion #accordion class="block space-y-2">
          <headwind-accordion-button
            class="flex items-center justify-between rounded-md bg-rose-500 px-3 py-2 text-sm text-white transition-colors hover:bg-rose-400"
          >
            <div>
              {{ person.name }}
            </div>

            <svg
              [@rotate]="accordion.opened ? 'rotated' : 'none'"
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="h-4 w-4"
            >
              <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
            </svg>
          </headwind-accordion-button>

          <ng-template headwindAccordionContent>
            <div [@expand] class="space-y-4 overflow-hidden py-3 text-sm">
              <div class="flex items-center space-x-1">
                <img
                  [src]="person.avatarUrl"
                  [alt]="person.name"
                  class="block h-10 w-10 rounded-full bg-zinc-200 object-cover"
                />

                <div>
                  <div class="font-semibold">
                    {{ person.name }}
                  </div>

                  <div class="text-xs text-zinc-400">
                    {{ person.job }}
                  </div>
                </div>
              </div>

              <div class="space-y-1">
                <div class="text-xs text-zinc-400">Biography</div>

                <div>
                  {{ person.biography }}
                </div>
              </div>
            </div>
          </ng-template>
        </headwind-accordion>
      }
    </div>
  `,
  animations: [
    trigger('expand', [
      state(
        'void',
        style({
          height: 0,
        }),
      ),
      transition(
        'void => *',
        animate(
          '.1s',
          style({
            height: '*',
          }),
        ),
      ),
      transition(
        '* => void',
        animate(
          '.1s',
          style({
            height: 0,
          }),
        ),
      ),
    ]),
    trigger('rotate', [
      state(
        'none',
        style({
          transform: 'rotate(0)',
        }),
      ),
      state(
        'rotated',
        style({
          transform: 'rotate(180deg)',
        }),
      ),
      transition('none <=> rotated', animate('.1s')),
    ]),
  ],
})
export class BasicAccordionExampleComponent {
  people = [
    {
      name: 'Tori Day',
      avatarUrl: 'https://picsum.photos/id/174/64',
      job: 'Designer',
      biography:
        'I can fit 6 brushes and pencils on each of my fingers and draw a picture. When working on a computer, I attach 4 mouses to my hands and feet, allowing me to work at a speed 5 times faster than others.',
    },
    {
      name: 'Ishaan Martinez',
      avatarUrl: 'https://picsum.photos/id/213/64',
      job: 'Full-Stack Developer',
      biography:
        'From the day I was born, I was a coding prodigy who screamed "console.log(\'Waah~ Waah~\')". At the age of 3, I mastered JavaScript and TypeScript, and by the time I turned 7, I had evolved into a flawless full-stack developer.',
    },
    {
      name: 'Alvin Bailey',
      avatarUrl: 'https://picsum.photos/id/322/64',
      job: 'Lawyer',
      biography: 'I am an ordinary lawyer.',
    },
  ];
}

Accordion Classes

Basic Classes

Each component used to implement Accordion has the same class name as its name. You can set styles using the class name as a selector in a style file.

import { Component } from '@angular/core';
import {
  HeadwindAccordionButtonComponent,
  HeadwindAccordionComponent,
  HeadwindAccordionContentDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-accordion-classes-example',
  standalone: true,
  imports: [HeadwindAccordionComponent, HeadwindAccordionButtonComponent, HeadwindAccordionContentDirective],
  template: `
    <headwind-accordion>
      <headwind-accordion-button> Button </headwind-accordion-button>

      <ng-template headwindAccordionContent>
        <div class="mt-3">Content</div>
      </ng-template>
    </headwind-accordion>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-accordion {
          @apply block w-full max-w-[400px] rounded-2xl bg-white p-8 shadow;
        }

        .headwind-accordion-button {
          @apply flex items-center justify-between rounded-md bg-rose-500 px-3 py-2 text-sm text-white transition-colors hover:bg-rose-400;
        }
      }
    `,
  ],
})
export class AccordionClassesExampleComponent {}

Opened Class

When an Accordion is open, the <headwind-accordion> component has the class .headwind-opened.

import { Component } from '@angular/core';
import {
  HeadwindAccordionButtonComponent,
  HeadwindAccordionComponent,
  HeadwindAccordionContentDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-accordion-opened-class-example',
  standalone: true,
  imports: [HeadwindAccordionComponent, HeadwindAccordionButtonComponent, HeadwindAccordionContentDirective],
  template: `
    <headwind-accordion>
      <headwind-accordion-button> Button</headwind-accordion-button>

      <ng-template headwindAccordionContent>
        <div class="mt-3">Content</div>
      </ng-template>
    </headwind-accordion>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-accordion {
          @apply block w-full max-w-[400px] rounded-2xl bg-white p-8 shadow;

          &.headwind-opened {
            .headwind-accordion-button {
              @apply bg-indigo-400;
            }
          }
        }

        .headwind-accordion-button {
          @apply flex items-center justify-between rounded-md bg-rose-500 px-3 py-2 text-sm text-white transition-colors hover:bg-rose-400;
        }
      }
    `,
  ],
})
export class AccordionOpenedClassExampleComponent {}

Opened State

You can use the opened attribute to change the display state of the content. When the opened state changes, the changed value is emitted through the openedChange emitter.

import { Component } from '@angular/core';
import {
  HeadwindAccordionButtonComponent,
  HeadwindAccordionComponent,
  HeadwindAccordionContentDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-accordion-opened-state-example',
  standalone: true,
  imports: [HeadwindAccordionComponent, HeadwindAccordionButtonComponent, HeadwindAccordionContentDirective],
  template: `
    <headwind-accordion
      [(opened)]="opened"
      class="block w-full max-w-[400px] space-y-2 rounded-2xl bg-white p-8 shadow"
    >
      <headwind-accordion-button
        class="block rounded-md bg-rose-500 px-3 py-2 text-sm text-white transition-colors hover:bg-rose-400"
      >
        Button Opened: {{ opened }}
      </headwind-accordion-button>

      <ng-template headwindAccordionContent>
        <div class="mt-3">Content</div>
      </ng-template>
    </headwind-accordion>
  `,
})
export class AccordionOpenedStateExampleComponent {
  opened = true;
}

toggle, open, close

The <headwind-accordion> component provides the toggle(), open(), and close() methods.

importimport { Component } from '@angular/core';
import {
  HeadwindAccordionButtonComponent,
  HeadwindAccordionComponent,
  HeadwindAccordionContentDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-accordion-methods-example',
  standalone: true,
  imports: [HeadwindAccordionComponent, HeadwindAccordionButtonComponent, HeadwindAccordionContentDirective],
  template: `
    <div class="w-full max-w-[400px]">
      <div class="flex flex-wrap items-stretch gap-2">
        <button
          (click)="accordion.toggle()"
          class="min-h-8 flex-[1_1_0] rounded-md bg-rose-500 px-3 text-sm text-white"
        >
          Toggle Accordion
        </button>
        <button (click)="accordion.open()" class="min-h-8 flex-[1_1_0] rounded-md bg-rose-500 px-3 text-sm text-white">
          Open Accordion
        </button>
        <button (click)="accordion.close()" class="min-h-8 flex-[1_1_0] rounded-md bg-rose-500 px-3 text-sm text-white">
          Close Accordion
        </button>
      </div>

      <headwind-accordion #accordion class="mt-3 block space-y-2 rounded-2xl bg-white p-8 shadow">
        <headwind-accordion-button
          class="block rounded-md bg-rose-500 px-3 py-2 text-sm text-white transition-colors hover:bg-rose-400"
        >
          Button
        </headwind-accordion-button>

        <ng-template headwindAccordionContent>
          <div class="mt-3">Content</div>
        </ng-template>
      </headwind-accordion>
    </div>
  `,
})
export class AccordionMethodsExampleComponent {}

Animation

Bind an animation trigger to *headwindAccordionContent to play animation when content is toggled.

import { Component } from '@angular/core';
import {
  HeadwindAccordionButtonComponent,
  HeadwindAccordionComponent,
  HeadwindAccordionContentDirective,
} from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-accordion-animation-example',
  standalone: true,
  imports: [HeadwindAccordionButtonComponent, HeadwindAccordionComponent, HeadwindAccordionContentDirective],
  template: `
    <headwind-accordion class="mt-3 block w-full max-w-[400px] space-y-2 rounded-2xl bg-white p-8 shadow">
      <headwind-accordion-button
        class="block rounded-md bg-rose-500 px-3 py-2 text-sm text-white transition-colors hover:bg-rose-400"
      >
        Button
      </headwind-accordion-button>

      <ng-template headwindAccordionContent>
        <div [@expand] class="mt-3">Content</div>
      </ng-template>
    </headwind-accordion>
  `,
  animations: [
    trigger('expand', [
      state(
        'void',
        style({
          height: 0,
        }),
      ),
      transition(
        'void => *',
        animate(
          '.1s',
          style({
            height: '*',
          }),
        ),
      ),
      transition(
        '* => void',
        animate(
          '.1s',
          style({
            height: 0,
          }),
        ),
      ),
    ]),
  ],
})
export class AccordionAnimationExampleComponent {}

Accessibility

Mouse Interaction

Clicking on the <headwind-accordion-button> element toggles its contents.

Keyboard Interaction

| Command | Target | Description | | ---- | ---- | ---- | | Space, Enter | <headwind-accordion-button> | Toggle the contents. |

API

HeadwindAccordionComponent

The root component of Accordion.

Selector

<headwind-accordion>

Host Class

.headwind-accordion .headwind-opened when Accordion opened.

Inputs

| Name | Description | | ---- | ---- | | @Input() set opened(value: boolean) | Set the opened state of content. |

Outputs

| Name | Description | | ---- | ---- | | @Output() openedChange: EventEmitter<boolean> | Emits the changed opened state of content. |

Methods

| Name | Description | | ---- | ---- | | toggle() | Toggle the opened state of Content. | | open() | Open the content. If it is already open, it is ignored. | | close() | Close the content. If it is already closed, it is ignored. |

HeadwindAccordionButtonComponent

A component that can open and close the Accordion content.

Selector

<headwind-accordion-button>

Host Class

.headwind-accordion-button

HeadwindAccordionContentDirective

A directive for a template that wraps Accordion content.

Selector

ng-template[headwindAccordionContent]

Checkbox

Component that toggles boolean state.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-basic-checkbox-example',
  standalone: true,
  imports: [HeadwindCheckboxComponent, HeadwindCheckboxButtonComponent, HeadwindCheckboxCheckedDirective],
  template: `
    <headwind-checkbox #checkbox class="inline-flex cursor-pointer select-none items-center space-x-1">
      <headwind-checkbox-button
        [class]="checkbox.checked ? 'border-rose-500 bg-rose-500' : 'bg-white'"
        class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300"
      >
        <ng-template headwindCheckboxChecked>
          <svg
            [@scaleUp]
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="absolute h-4 w-4 stroke-white"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
          </svg>
        </ng-template>
      </headwind-checkbox-button>

      <label class="cursor-pointer select-none text-sm"> I like Headwind UI </label>
    </headwind-checkbox>
  `,
  animations: [
    trigger('scaleUp', [
      state(
        'void',
        style({
          transform: 'scale(0)',
        }),
      ),
      transition(
        'void => *',
        animate(
          '.1s',
          style({
            transform: 'scale(1)',
          }),
        ),
      ),
      transition('* => void', animate('.1s')),
    ]),
  ],
})
export class BasicCheckboxExampleComponent {}

Checkbox Classes

Basic Classes

Each component used to implement Checkbox has the same class name as its name. You can set styles using the class name as a selector in a style file.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-checkbox-classes-example',
  standalone: true,
  imports: [HeadwindCheckboxComponent, HeadwindCheckboxButtonComponent, HeadwindCheckboxCheckedDirective],
  template: `
    <headwind-checkbox>
      <headwind-checkbox-button>
        <ng-template headwindCheckboxChecked>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="absolute h-4 w-4 stroke-rose-500"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
          </svg>
        </ng-template>
      </headwind-checkbox-button>
    </headwind-checkbox>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-checkbox {
          @apply inline-block cursor-pointer select-none;
        }

        .headwind-checkbox-button {
          @apply relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white;
        }
      }
    `,
  ],
})
export class CheckboxClassesExampleComponent {}

Checked Class

When a Checkbox is checked, the <headwind-checkbox> component has the class .headwind-checked.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-checkbox-checked-class-example',
  standalone: true,
  imports: [HeadwindCheckboxComponent, HeadwindCheckboxButtonComponent, HeadwindCheckboxCheckedDirective],
  template: `
    <headwind-checkbox>
      <headwind-checkbox-button>
        <ng-template headwindCheckboxChecked>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="absolute h-4 w-4 stroke-white"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
          </svg>
        </ng-template>
      </headwind-checkbox-button>
    </headwind-checkbox>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-checkbox {
          @apply inline-block cursor-pointer select-none;

          &.headwind-checked {
            .headwind-checkbox-button {
              @apply border-rose-500 bg-rose-500;
            }
          }
        }

        .headwind-checkbox-button {
          @apply relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white;
        }
      }
    `,
  ],
})
export class CheckboxCheckedClassExampleComponent {}

Checked State

You can use the checked attribute to toggle the checked state. When the checked state changes, the changed value is emitted through the checkedChange emitter.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-checkbox-checked-state-example',
  standalone: true,
  imports: [HeadwindCheckboxComponent, HeadwindCheckboxButtonComponent, HeadwindCheckboxCheckedDirective],
  template: `
    <headwind-checkbox [(checked)]="checked" class="inline-flex cursor-pointer select-none items-center space-x-1">
      <headwind-checkbox-button
        [class]="checked ? 'border-rose-500 bg-rose-500' : 'bg-white'"
        class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300"
      >
        <ng-template headwindCheckboxChecked>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="absolute h-4 w-4 stroke-white"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
          </svg>
        </ng-template>
      </headwind-checkbox-button>
    </headwind-checkbox>
  `,
})
export class CheckboxCheckedStateExampleComponent {
  checked = true;
}

toggle, check, uncheck

The <headwind-checkbox> component provides the toggle(), check(), and uncheck() methods.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-checkbox-methods-example',
  standalone: true,
  imports: [HeadwindCheckboxComponent, HeadwindCheckboxButtonComponent, HeadwindCheckboxCheckedDirective],
  template: `
    <div class="w-full max-w-[200px]">
      <div class="flex flex-col items-stretch gap-2">
        <button (click)="checkbox.toggle()" class="h-8 rounded-md bg-rose-500 px-3 text-sm text-white">
          Toggle Checkbox
        </button>
        <button (click)="checkbox.check()" class="h-8 rounded-md bg-rose-500 px-3 text-sm text-white">
          Check Checkbox
        </button>
        <button (click)="checkbox.uncheck()" class="h-8 rounded-md bg-rose-500 px-3 text-sm text-white">
          Uncheck Checkbox
        </button>

        <div class="flex justify-center">
          <headwind-checkbox #checkbox class="inline-flex cursor-pointer select-none items-center space-x-1">
            <headwind-checkbox-button
              class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white"
            >
              <ng-template headwindCheckboxChecked>
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                  class="absolute h-4 w-4 stroke-rose-500"
                >
                  <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
                </svg>
              </ng-template>
            </headwind-checkbox-button>
          </headwind-checkbox>
        </div>
      </div>
    </div>
  `,
})
export class CheckboxMethodsExampleComponent {}

NgModel, FormControl

The <headwind-checkbox> component can bind value using NgModel or FormControl.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-checkbox-model-binding-example',
  standalone: true,
  imports: [
    HeadwindCheckboxComponent,
    HeadwindCheckboxButtonComponent,
    HeadwindCheckboxCheckedDirective,
    FormsModule,
    ReactiveFormsModule,
  ],
  template: `
    <div class="flex flex-col items-stretch gap-2">
      <headwind-checkbox [(ngModel)]="checked" class="inline-flex cursor-pointer select-none items-center space-x-1">
        <headwind-checkbox-button
          class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white"
        >
          <ng-template headwindCheckboxChecked>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="absolute h-4 w-4 stroke-rose-500"
            >
              <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
            </svg>
          </ng-template>
        </headwind-checkbox-button>

        <label class="cursor-pointer select-none text-sm"> With NgModel: {{ checked }} </label>
      </headwind-checkbox>

      <headwind-checkbox
        [formControl]="checkControl"
        class="inline-flex cursor-pointer select-none items-center space-x-1"
      >
        <headwind-checkbox-button
          class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white"
        >
          <ng-template headwindCheckboxChecked>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="absolute h-4 w-4 stroke-rose-500"
            >
              <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/>
            </svg>
          </ng-template>
        </headwind-checkbox-button>

        <label class="cursor-pointer select-none text-sm"> With FormControl: {{ checkControl.value }} </label>
      </headwind-checkbox>
    </div>
  `,
})
export class CheckboxModelBindingExampleComponent {
  checked = false;
  checkControl = new FormControl(false);
}

Disabled State

Setting the disabled attribute will disable the checkbox. Styling for the disabled state uses the [disabled] CSS selector instead of :disabled.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-checkbox-disabled-example',
  standalone: true,
  imports: [HeadwindCheckboxComponent, HeadwindCheckboxButtonComponent, HeadwindCheckboxCheckedDirective],
  template: `
    <div class="flex flex-col gap-2">
      <headwind-checkbox disabled checked class="inline-flex cursor-pointer select-none items-center space-x-1">
        <headwind-checkbox-button
          class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white"
        >
          <ng-template headwindCheckboxChecked>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="absolute h-4 w-4 stroke-rose-500"
            >
              <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
            </svg>
          </ng-template>
        </headwind-checkbox-button>

        <label class="cursor-pointer select-none text-sm"> Disabled with Checked </label>
      </headwind-checkbox>

      <headwind-checkbox disabled class="inline-flex cursor-pointer select-none items-center space-x-1">
        <headwind-checkbox-button
          class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white"
        >
          <ng-template headwindCheckboxChecked>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="absolute h-4 w-4 stroke-rose-500"
            >
              <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
            </svg>
          </ng-template>
        </headwind-checkbox-button>

        <label class="cursor-pointer select-none text-sm"> Disabled with Unchecked </label>
      </headwind-checkbox>

      <headwind-checkbox class="inline-flex cursor-pointer select-none items-center space-x-1">
        <headwind-checkbox-button
          class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white"
        >
          <ng-template headwindCheckboxChecked>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="absolute h-4 w-4 stroke-rose-500"
            >
              <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
            </svg>
          </ng-template>
        </headwind-checkbox-button>

        <label class="cursor-pointer select-none text-sm"> Not Disabled </label>
      </headwind-checkbox>
    </div>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-checkbox[disabled] {
          @apply cursor-not-allowed;

          .headwind-checkbox-button {
            @apply bg-zinc-200;

            svg {
              @apply stroke-zinc-400;
            }
          }

          label {
            @apply cursor-not-allowed text-zinc-400;
          }
        }
      }
    `,
  ],
})
export class CheckboxDisabledExampleComponent {}

Animation

Bind an animation trigger to [headwindCheckboxChecked] to play animation when checked state is toggled.

import { Component } from '@angular/core';
import {
  HeadwindCheckboxButtonComponent,
  HeadwindCheckboxCheckedDirective,
  HeadwindCheckboxComponent,
} from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-checkbox-animation-example',
  standalone: true,
  imports: [HeadwindCheckboxComponent, HeadwindCheckboxButtonComponent, HeadwindCheckboxCheckedDirective],
  template: `
    <headwind-checkbox class="inline-flex cursor-pointer select-none items-center space-x-1">
      <headwind-checkbox-button
        class="relative flex h-5 w-5 items-center justify-center rounded border border-zinc-300 bg-white"
      >
        <ng-template headwindCheckboxChecked>
          <svg
            [@scaleUp]
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="absolute h-4 w-4 stroke-rose-500"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
          </svg>
        </ng-template>
      </headwind-checkbox-button>
    </headwind-checkbox>
  `,
  animations: [
    trigger('scaleUp', [
      state(
        'void',
        style({
          transform: 'scale(0)',
        }),
      ),
      transition(
        'void => *',
        animate(
          '.1s',
          style({
            transform: 'scale(1)',
          }),
        ),
      ),
      transition('* => void', animate('.1s')),
    ]),
  ],
})
export class CheckboxAnimationExampleComponent {}

Accessibility

Mouse Interaction

Clicking on the <headwind-checkbox> element toggles its checked state.

Keyboard Interaction

| Command | Target | Description | | ---- | ---- | ---- | | Space | <headwind-checkbox> | Toggle the checked state. |

API

HeadwindCheckboxComponent

The root component of Checkbox.

Selector

<headwind-checkbox>

Host Class

.headwind-checkbox .headwind-checked when Checkbox checked.

Inputs

| Name | Description | | ---- | ---- | | @Input() set checked(value: boolean) | Set the checked state of checkbox. | | @Input() set disabled(value: boolean) | Set the disabled state of checkbox. |

Outputs

| Name | Description | | ---- | ---- | | @Output() checkedChange: EventEmitter<boolean> | Emits the changed checked state of checkbox. |

Methods

| Name | Description | | ---- | ---- | | toggle() | Toggle the checked state of Checkbox. | | check() | Check the checkbox. If it is already checked, it is ignored. | | uncheck() | Uncheck the checkbox. If it is already unchecked, it is ignored. |

HeadwindCheckboxButtonComponent

A component for drawing a checkbox containing a checked icon.

Selector

<headwind-checkbox-button>

Host Class

.headwind-checkbox-button

HeadwindCheckboxCheckedDirective

A directive for a template that wraps a checked indicator.

Selector

ng-template[headwindCheckboxChecked]

Overlay

Creates a transparent layer that appears layered on top of other elements. It can be used when implementing screens such as modals, pop-ups, dialog boxes, tooltips, etc.

To create an overlay, HeadwindOverlayService and TemplateRef are required. Pass a TemplateRef to use as an overlay to the open() method. The open() method returns EmbeddedViewRef.

To close the overlay, use the destroy() method of EmbeddedViewRef.

import { Component, EmbeddedViewRef, OnDestroy, TemplateRef } from '@angular/core';
import { HeadwindOverlayService } from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-basic-overlay-example',
  standalone: true,
  imports: [],
  template: `
    <button (click)="open(modal)" class="h-8 rounded-md bg-rose-500 px-3 text-sm text-white">Open Overlay</button>

    <ng-template #modal>
      <div [@fadeIn]="'show'" class="absolute h-full w-full bg-white/[0.3] backdrop-blur-md"></div>

      <div [@fadeIn]="'show'" class="absolute flex h-full w-full items-center justify-center p-8">
        <div class="w-full max-w-[400px] space-y-4 rounded-2xl bg-white p-8 shadow-2xl">
          <div class="text-lg font-semibold">It's Headwind Overlay</div>

          <div class="text-sm">Any TemplateRef can be opened as an overlay.</div>

          <button
            (click)="openedModal?.destroy()"
            class="h-10 w-full rounded-md border border-zinc-200 text-sm font-semibold transition-colors hover:bg-zinc-200"
          >
            Okay
          </button>
        </div>
      </div>
    </ng-template>
  `,
  animations: [
    trigger('fadeIn', [
      state(
        'void',
        style({
          opacity: 0,
        }),
      ),
      state(
        'show',
        style({
          opacity: 1,
        }),
      ),
      transition('void <=> show', animate('.1s')),
    ]),
  ],
})
export class BasicOverlayExampleComponent implements OnDestroy {
  openedModal?: EmbeddedViewRef<any>;

  constructor(private readonly _headwindOverlayService: HeadwindOverlayService) {}

  ngOnDestroy() {
    this.openedModal?.destroy();
  }

  open(templateRef: TemplateRef<any>): void {
    if (!this.openedModal) {
      this.openedModal = this._headwindOverlayService.open(templateRef, () => delete this.openedModal);
    }
  }
}

HeadwindOverlayOutlet

When an overlay is created, <headwind-overlay-outlet> is created dynamically. <headwind-overlay-outlet> is a fixed component that covers the entire screen, and user interaction is not possible because pointer-events: none is set.

Child elements of <headwind-overlay-outlet> are set to pointer-events: auto so all interactions are possible.

Accessibility

Keyboard Interaction

| Command | Target | Description | | ---- | ---- | ---- | | Escape | window | Close the latest overlay. |

API

HeadwindOverlayService

Methods

| Name | Description | | ---- | ---- | | open<C = any>(templateRef: TemplateRef<C>, onDestroy?: () => void): EmbeddedViewRef<C> | Open a template as overlay. Can pass onDestroy callback function to call when destroying EmbeddedViewRef | | closeLatest() | Close the latest overlay. |

Popover

A small information box that appears when you click or hover over an element and is used to provide additional content or interact with the user.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-basic-popover-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverButtonDirective,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
  ],
  template: `
    <headwind-popover>
      <div class="flex w-[250px] items-center justify-between rounded-2xl bg-white p-4 shadow-xl">
        <div class="flex items-center space-x-2">
          <img [src]="user.avatarUrl" [alt]="user.name" class="block h-10 w-10 rounded-full bg-zinc-100" />

          <div>
            <div class="font-semibold">
              {{ user.name }}
            </div>

            <div class="text-sm text-zinc-400">
              {{ user.job }}
            </div>
          </div>
        </div>

        <svg
          headwindPopoverButton
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          class="h-6 w-6 cursor-pointer stroke-zinc-400"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
          />
        </svg>
      </div>

      <ng-template headwindPopoverOverlay>
        <headwind-popover-overlay
          [@fadeIn]
          pinPriority="left"
          directionPriority="bottom"
          class="block w-full max-w-[250px] space-y-4 rounded-xl bg-black/[0.8] p-4 text-xs text-white shadow-2xl"
        >
          {{ user.biography }}
        </headwind-popover-overlay>
      </ng-template>
    </headwind-popover>
  `,
  animations: [
    trigger('fadeIn', [
      state(
        'void',
        style({
          opacity: 0,
        }),
      ),
      transition(
        'void => *',
        animate(
          '.1s',
          style({
            opacity: 1,
          }),
        ),
      ),
      transition('* => void', animate('.1s')),
    ]),
  ],
})
export class BasicPopoverExampleComponent {
  user = {
    name: 'Tori Day',
    avatarUrl: 'https://picsum.photos/id/174/64',
    job: 'Designer',
    biography:
      'I can fit 6 brushes and pencils on each of my fingers and draw a picture. When working on a computer, I attach 4 mouses to my hands and feet, allowing me to work at a speed 5 times faster than others.',
  };
}

Popover Classes

Each component used to implement Popover has the same class name as its name. You can set styles using the class name as a selector in a style file.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-popover-classes-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverButtonDirective,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
  ],
  template: `
    <headwind-popover>
      <button headwindPopoverButton>Open</button>

      <ng-template headwindPopoverOverlay>
        <headwind-popover-overlay> Popover Overlay </headwind-popover-overlay>
      </ng-template>
    </headwind-popover>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-popover-button {
          @apply h-10 rounded-md bg-rose-500 px-3 text-sm text-white;
        }

        .headwind-popover-overlay {
          @apply block w-full max-w-[250px] space-y-4 rounded-xl bg-white p-4 text-sm shadow-2xl;
        }
      }
    `,
  ],
})
export class PopoverClassesExampleComponent {}

direction

<headwind-popover-overlay> can specify the display direction using the direction attribute. You can specify 'vertical' or 'horizontal', the default is vertical.

When vertical, the overlay is displayed above or below [headwindPopoverButton], and when horizontal, it is displayed on the left or right.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-popover-direction-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
    HeadwindPopoverButtonDirective,
  ],
  template: `
    <div class="flex w-full max-w-[400px] flex-col items-center space-y-2">
      <headwind-popover>
        <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
          Open Vertical Popover
        </button>

        <ng-template headwindPopoverOverlay>
          <headwind-popover-overlay direction="vertical" class="rounded-md bg-white p-4 text-sm shadow-xl">
            Vertical Popover
          </headwind-popover-overlay>
        </ng-template>
      </headwind-popover>

      <headwind-popover>
        <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
          Open Horizontal Popover
        </button>

        <ng-template headwindPopoverOverlay>
          <headwind-popover-overlay direction="horizontal" class="rounded-md bg-white p-4 text-sm shadow-xl">
            Horizontal Popover
          </headwind-popover-overlay>
        </ng-template>
      </headwind-popover>
    </div>
  `,
})
export class PopoverDirectionExampleComponent {}

Direction Priority

<headwind-popover-overlay> sets the display direction priority using the directionPriority attribute. When direction is vertical, you can set top or bottom, and when direction is horizontal, you can set left or right.

If directionPriority is set, the popover overlay will display in the set direction whenever possible. If there is not enough space in that direction, it will be displayed in the opposite direction.

When direction is vertical, the default value is bottom, and when horizontal, the default value is right.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-popover-direction-priority-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
    HeadwindPopoverButtonDirective,
  ],
  template: `
    <div class="flex w-full max-w-[400px] flex-col items-center space-y-2">
      <headwind-popover>
        <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
          Open Top Direction Priority Popover
        </button>

        <ng-template headwindPopoverOverlay>
          <headwind-popover-overlay
            direction="vertical"
            directionPriority="top"
            class="rounded-md bg-white p-4 text-sm shadow-xl"
          >
            Top Direction Priority Popover
          </headwind-popover-overlay>
        </ng-template>
      </headwind-popover>

      <headwind-popover>
        <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
          Open Left Direction Priority Popover
        </button>

        <ng-template headwindPopoverOverlay>
          <headwind-popover-overlay
            direction="horizontal"
            directionPriority="left"
            class="rounded-md bg-white p-4 text-sm shadow-xl"
          >
            Left Direction Priority Popover
          </headwind-popover-overlay>
        </ng-template>
      </headwind-popover>
    </div>
  `,
})
export class PopoverDirectionPriorityExampleComponent {}

Pin Priority

Popover overlays are pinned in one direction. You can also set the priority in the pin direction using the pinPriority attribute. If direction is vertical, pin priority can be set to left or right, and if horizontal, pin priority can be set to top or bottom.

If pinPriority is set, the popover overlay will be pinned to the configured side whenever possible. If there is not enough space to pin on that side, it will be pinned on the other side.

If direction is vertical, the default is left, if horizontal, the default is top.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-popover-pin-priority-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
    HeadwindPopoverButtonDirective,
  ],
  template: `
    <div class="flex w-full max-w-[400px] flex-col items-center space-y-2">
      <headwind-popover>
        <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
          Open Right Pin Priority Popover
        </button>

        <ng-template headwindPopoverOverlay>
          <headwind-popover-overlay
            pinPriority="right"
            direction="vertical"
            directionPriority="top"
            class="rounded-md bg-white p-4 text-sm shadow-xl"
          >
            Right Pin Priority Popover
          </headwind-popover-overlay>
        </ng-template>
      </headwind-popover>

      <headwind-popover>
        <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
          Open Bottom Pin Priority Popover
        </button>

        <ng-template headwindPopoverOverlay>
          <headwind-popover-overlay
            pinPriority="bottom"
            direction="horizontal"
            directionPriority="left"
            class="rounded-md bg-white p-4 text-sm shadow-xl"
          >
            Bottom Pin Priority Popover
          </headwind-popover-overlay>
        </ng-template>
      </headwind-popover>
    </div>
  `,
})
export class PopoverPinPriorityExampleComponent {}

actualDirectionChange, actualPinChange

Popover overlay uses RequestAnimationFrame to dynamically change its position depending on its position on the screen. And the actual rendered direction and pin are emitted through the actualDirectionChange emitter and actualPinChange emitter.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';
import { XYDirections } from '../../../../../../../projects/headwind-ui/src/types/xy-directions';

@Component({
  selector: 'app-popover-actual-change-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverButtonDirective,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
  ],
  template: `
    <headwind-popover>
      <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">Open Popover</button>

      <ng-template headwindPopoverOverlay>
        <headwind-popover-overlay
          (actualDirectionChange)="actualDirection = $event"
          (actualPinChange)="actualPin = $event"
          class="block w-full max-w-[300px] space-y-4 rounded-md bg-white p-4 text-sm shadow-xl"
        >
          <div class="text-lg font-semibold">Changed Directions</div>

          <div class="space-y-1 text-sm">
            <div>Actual Direction: {{ actualDirection }}</div>

            <div>Actual Pin: {{ actualPin }}</div>
          </div>
        </headwind-popover-overlay>
      </ng-template>
    </headwind-popover>
  `,
})
export class PopoverActualChangeExampleComponent {
  actualDirection: XYDirections = 'bottom';
  actualPin: XYDirections = 'left';
}

open, close

<headwind-popover> has open() and close() methods for opening and closing the popover overlay.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-popover-methods-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverButtonDirective,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
  ],
  template: `
    <div class="flex flex-col items-center space-y-2">
      <button (click)="popover.open()" class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
        Outer Popover Button
      </button>

      <headwind-popover #popover>
        <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">
          Inner Popover Button
        </button>

        <ng-template headwindPopoverOverlay>
          <headwind-popover-overlay class="w-full max-w-[250px] rounded-md bg-white p-4 text-sm shadow-xl">
            Popover overlays are closed by clicking outside the overlay or by pressing the Escape key.
          </headwind-popover-overlay>
        </ng-template>
      </headwind-popover>
    </div>
  `,
})
export class PopoverMethodsExampleComponent {}

Animation

Bind an animation trigger to <headwind-popover-overlay> to play animation when popover overlay is displayed or hidden.

import { Component } from '@angular/core';
import {
  HeadwindPopoverButtonDirective,
  HeadwindPopoverComponent,
  HeadwindPopoverOverlayComponent,
  HeadwindPopoverOverlayTemplateDirective,
} from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-popover-animation-example',
  standalone: true,
  imports: [
    HeadwindPopoverComponent,
    HeadwindPopoverButtonDirective,
    HeadwindPopoverOverlayComponent,
    HeadwindPopoverOverlayTemplateDirective,
  ],
  template: `
    <headwind-popover>
      <button headwindPopoverButton class="h-10 rounded-md bg-rose-500 px-3 text-sm text-white">Open</button>

      <ng-template headwindPopoverOverlay>
        <headwind-popover-overlay
          [@fadeIn]
          class="block w-full max-w-[250px] rounded-xl bg-white p-4 text-sm shadow-2xl"
        >
          With Animation
        </headwind-popover-overlay>
      </ng-template>
    </headwind-popover>
  `,
  animations: [
    trigger('fadeIn', [
      state(
        'void',
        style({
          opacity: 0,
        }),
      ),
      transition(
        'void => *',
        animate(
          '.1s',
          style({
            opacity: 1,
          }),
        ),
      ),
      transition('* => void', animate('.1s')),
    ]),
  ],
})
export class PopoverAnimationExampleComponent {}

Accessibility

Mouse Interaction

Clicking on the [headwindPopoverButton] element opens popover overlay. Clicking outside of <headwind-popover-overlay> closes the popover overlay.

Keyboard Interaction

| Command | Target | Description | | ---- | ---- | ---- | | Escape | window | Close the popover overlay when it is opened. |

API

HeadwindPopoverComponent

Popover's container component. All components or directives related to Popover must be inside this component.

Selector

<headwind-popover>

Host Class

.headwind-popover

Methods

| Name | Description | | ---- | ---- | | open() | Open the popover overlay. If it is already opened, it is ignored. | | close() | Closes the popover overlay. If it is already closed, it is ignored. |

HeadwindPopoverButtonDirective

A directive to bind to the button that will be used to open the Popover overlay.

Selector

[headwindPopoverButton]

Host Class

.headwind-popover-button

HeadwindPopoverOverlayComponent

This is a component to be used as an overlay. Must be used with [*headwindPopoverOverlayTemplate].

Selector

<headwind-popover-overlay>

Host Class

.headwind-popover-overlay

Inputs

| Name | Description | | ---- | ---- | | @Input() direction: LinearDirection | Set the direction in which the Popover overlay will be displayed. When vertical, it is displayed as the top or bottom of the button, and when horizontal, it is displayed to the left or right of the button. The default is vertical.. | | @Input() directionPriority: XYdirection | Set the priority of the direction in which the Popover overlay will be displayed. When direction is vertical, you can set top or bottom, and when direction is horizontal, you can set left or right. When direction is vertical, the default value is bottom, and when horizontal, the default value is right. | | @Input() pinPriority: XYDirection | Set the priority of the side on which the popover overlay is pinned. If direction is vertical, pin priority can be set to left or right, and if horizontal, pin priority can be set to top or bottom. If direction is vertical, the default is left, if horizontal, the default is top. |

Outputs

| Name | Description | | ---- | ---- | | @Output() actualDirectionChange: EventEmitter<XYDirection> | The direction in which the actual popover overlay is rendered is emitted every RequestAnimationFrame. | | @Output() actualPinChange: EventEmitter<XYDirection> | The pin side where the actual popover overlay is rendered is emitted every RequestAnimationFrame. |

HeadwindPopoverOverlayDirective

A directive for the template that wraps <headwind-popover-overlay>.

Selector

ng-template[headwindPopoverOverlay]

Radio Group

A component that allows the user to select one of several choices. Multiple radio buttons grouped together.

import { Component } from '@angular/core';
import {
  HeadwindRadioButtonComponent,
  HeadwindRadioComponent,
  HeadwindRadioGroupComponent,
  HeadwindRadioSelectedDirective,
} from '@favian/headwind-ui';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-basic-radio-group-example',
  standalone: true,
  imports: [
    HeadwindRadioComponent,
    HeadwindRadioGroupComponent,
    HeadwindRadioButtonComponent,
    HeadwindRadioSelectedDirective,
  ],
  template: `
    <div class="w-full max-w-[400px] space-y-2 rounded-2xl bg-white p-8 shadow-xl">
      <label class="text-lg font-semibold"> Choose your partner </label>

      <headwind-radio-group class="block space-y-2">
        @for (option of options; track option.name) {
          <headwind-radio
            #radio
            [value]="option.name"
            class="flex cursor-pointer select-none items-center justify-between rounded-md border border-zinc-200 p-3 transition-colors hover:bg-rose-50"
          >
            <div>
              <div class="text-sm font-semibold">
                {{ option.name }}
              </div>

              <div class="text-xs text-zinc-400">
                {{ option.description }}
              </div>
            </div>

            <headwind-radio-button
              class="relative flex h-5 w-5 items-center justify-center rounded-full border border-zinc-300 transition-colors"
            >
              <ng-template headwindRadioSelected>
                <div [@scaleUp] class="absolute h-2 w-2 rounded-full bg-rose-500"></div>
              </ng-template>
            </headwind-radio-button>
          </headwind-radio>
        }
      </headwind-radio-group>
    </div>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-radio-group:focus {
          .headwind-radio.headwind-selected {
            .headwind-radio-button {
              @apply bg-rose-100;
            }
          }
        }

        .headwind-radio.headwind-selected {
          .headwind-radio-button {
            @apply border-rose-500;
          }
        }
      }
    `,
  ],
  animations: [
    trigger('scaleUp', [
      state(
        'void',
        style({
          transform: 'scale(0)',
        }),
      ),
      transition(
        'void => *',
        animate(
          '.1s',
          style({
            transform: 'scale(1)',
          }),
        ),
      ),
      transition('* => void', animate('.1s')),
    ]),
  ],
})
export class BasicRadioGroupExampleComponent {
  options = [
    {
      name: 'Tori Day',
      description: 'Epic Crazy Designer',
    },
    {
      name: 'Ishaan Martinez',
      description: 'Natural Born Computer-Human',
    },
    {
      name: 'Alvin Bailey',
      description: 'Common Layer',
    },
  ];
}

Radio Group Classes

Basic Classes

Each component used to implement Radio Group has the same class name as its name. You can set styles using the class name as a selector in a style file.

import { Component } from '@angular/core';
import {
  HeadwindRadioButtonComponent,
  HeadwindRadioComponent,
  HeadwindRadioGroupComponent,
  HeadwindRadioSelectedDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-radio-group-classes-example',
  standalone: true,
  imports: [
    HeadwindRadioGroupComponent,
    HeadwindRadioComponent,
    HeadwindRadioButtonComponent,
    HeadwindRadioSelectedDirective,
  ],
  template: `
    <headwind-radio-group>
      @for (option of options; track option) {
        <headwind-radio [value]="option">
          <headwind-radio-button>
            <ng-template headwindRadioSelected>
              <div class="absolute h-2 w-2 rounded-full bg-rose-500"></div>
            </ng-template>
          </headwind-radio-button>

          <div class="text-sm">
            {{ option }}
          </div>
        </headwind-radio>
      }
    </headwind-radio-group>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-radio-group {
          @apply block w-full max-w-[300px] space-y-2 rounded-xl bg-white p-6 shadow-xl;
        }

        .headwind-radio {
          @apply flex cursor-pointer select-none items-center space-x-2;
        }

        .headwind-radio-button {
          @apply relative flex h-5 w-5 items-center justify-center rounded-full border border-zinc-300;
        }
      }
    `,
  ],
})
export class RadioGroupClassesExampleComponent {
  options = ['Tori Day', 'Ishaan Martinez', 'Alvin Bailey'];
}

Selected Class

When a Radio is selected, the <headwind-radio> component has the class .headwind-selected.

import { Component } from '@angular/core';
import {
  HeadwindRadioButtonComponent,
  HeadwindRadioComponent,
  HeadwindRadioGroupComponent,
  HeadwindRadioSelectedDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-radio-group-selected-class-example',
  standalone: true,
  imports: [
    HeadwindRadioGroupComponent,
    HeadwindRadioComponent,
    HeadwindRadioButtonComponent,
    HeadwindRadioSelectedDirective,
  ],
  template: `
    <headwind-radio-group>
      @for (option of options; track option) {
        <headwind-radio [value]="option">
          <headwind-radio-button>
            <ng-template headwindRadioSelected>
              <div class="absolute h-2 w-2 rounded-full bg-rose-500"></div>
            </ng-template>
          </headwind-radio-button>

          <div class="text-sm">
            {{ option }}
          </div>
        </headwind-radio>
      }
    </headwind-radio-group>
  `,
  styles: [
    `
      @tailwind components;

      @layer components {
        .headwind-radio-group {
          @apply block w-full max-w-[300px] space-y-2 rounded-xl bg-white p-6 shadow-xl;
        }

        .headwind-radio {
          @apply flex cursor-pointer select-none items-center space-x-2;

          &.headwind-selected {
            .headwind-radio-button {
              @apply border-rose-500;
            }
          }
        }

        .headwind-radio-button {
          @apply relative flex h-5 w-5 items-center justify-center rounded-full border border-zinc-300;
        }
      }
    `,
  ],
})
export class RadioGroupSelectedClassExampleComponent {
  options = ['Tori Day', 'Ishaan Martinez', 'Alvin Bailey'];
}

Value

Radio Group can bind value to property using value input and valueChange output.

import { Component } from '@angular/core';
import {
  HeadwindRadioButtonComponent,
  HeadwindRadioComponent,
  HeadwindRadioGroupComponent,
  HeadwindRadioSelectedDirective,
} from '@favian/headwind-ui';

@Component({
  selector: 'app-radio-group-value-example',
  standalone: true,
  imports: [
    HeadwindRadioGroupComponent,
    HeadwindRadioComponent,
    HeadwindRadioButtonComponent,
    HeadwindRadioSelectedDirective,
  ],
  template: `
    <div class="w-full max-w-[300px] space-y-2">
      <div class="text-sm">Selected Value: {{ value }}</div>

      <headwind-radio-group [(value)]="value" class="block space-y-2 rounded-xl bg-white p-6 shadow-xl">
        @for (option of options; track option) {
          <headwind-radio [value]="option" class="flex cursor-pointer select-none items-center space-x-2">
            <headwind-radio-button
              class="relative flex h-5 w-5 items-center justify-center rounded-full border border-zinc-300"
            >
              <ng-template headwindRadioSelected>
                <div class="absolute h-2 w-2 rounded-full bg-rose-500"></div>
              </ng-template>
            </headwind-radio-button>

            <div class="text-sm">
              {{ option }}
            </div>
          </headwind-radio>
        }
      </headwind-radio-group>
    </div>
  `,
})
export class RadioGroupValueExampleComponent {
  value = 'Ishaan Martinez';

  options = ['Tori Day', 'Ishaan Martinez', 'Alvin Bailey'];
}

NgModel, FormControl

The <headwind-radio-group> component can bind value using NgModel or FormControl.

import { Component } from '@angular/core';
import {
  HeadwindRadioButtonComponent,
  HeadwindRadioComponent,
  HeadwindRadioGroupComponent,
  HeadwindRadioSelectedDirective,
} from '@favian/headwind-ui';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-radio-group-model-binding-example',
  standalone: true,
  imports: [
    HeadwindRadioGroupComponent,
    HeadwindRadioComponent,
    HeadwindRadioButtonComponent,
    HeadwindRadioSelectedDirective,
    FormsModule,
    ReactiveFormsModule,
  ],
  template: `
    <div class="w-full max-w-[300px] space-y-2">
      <div class="text-sm">Selected NgModel: {{ value }}</div>

      <headwind-radio-group [(ngModel)]="value" class="block space-y-2 rounded-xl bg-white p-6 shadow-xl">
        @for (option of options; track option) {
          <headwind-radio [value]="option" class="flex cursor-pointer select-none items-center space-x-2">
            <headwind-radio-button
              class="relative flex h-5 w-5 items-center justify-center rounded-full border border-zinc-300"
            >
              <ng-template headwindRadioSelected>
                <div class="absolute h-2 w-2 rounded-full bg-rose-500"></div>
              </ng-template>
            </headwind-radio-button>

            <div class="text-sm">
              {{ option }}
            </div>
          </headwind-radio>
        }
      </headwind-radio-group>

      <div class="text-sm">Selected FormControl: {{ formControl.value }}</div>

      <headwind-radio-group [formControl]="formControl" class="block space-y-2 rounded-xl bg-white p-6 shadow-xl">
        @for (option of options; track option) {
          <headwind-radio [value]="option" class="flex cursor-pointer select-none items-center space-x-2">
            <headwind-radio-button
              class="relative flex h-5 w-5 items-center justify-center rounded-full border border-zinc-300"
            >
              <ng-template headwindRadioSelected>
                <div class="absolute h-2 w-2 rounded-full bg-rose-500"></div>
              </ng-template>
            </headwind-radio-button>

            <div class="text-sm">
              {{ option }}
            </div>