@favian/headwind-ui
Version:
Headless UI for Angular - Styleless Angular components to integrate with Tailwind CSS
1,603 lines (1,368 loc) • 131 kB
Markdown
# Headwind UI
To see documentation with live examples, visit [Headwind UI Homepage](https://ng-headwind-ui.github.io).
# Getting Started
Headwind UI is a styleless Angular components library.<br/>
Designed to integrate with Tailwind CSS.
To get started, install Headwind UI via npm.
```shell
npm install @favian/headwind-ui
```
Some components of Headwind UI have a `tabindex` attribute for accessibility.<br/>
Set outline to `0` to disable the Browser's default focus effect.
```scss
@layer components {
[class*="headwind-"] {
@apply outline-0;
}
}
```
# Accordion
This is a component that can hide or show content using a button.
```typescript
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.
```typescript
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`.
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
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`<br/>
`.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.
```typescript
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.
```typescript
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`.
```typescript
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.
```typescript
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.
```typescript
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`.
```typescript
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`.
```typescript
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.
```typescript
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`<br/>
`.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](https://angular.io/api/core/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`.
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
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`.
```typescript
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`.
```typescript
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.
```typescript
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.
```typescript
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.