Drawer
The <smart-drawer> component provides a side panel overlay (left/right) with optional header, close button, and backdrop. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract DrawerBaseComponent defines the shared API — open (two-way ModelSignal<boolean>), title, optional IDrawerOptions, cssClass (alias class), a closed output, and a close() method that sets open to false and emits closed. DrawerStandardComponent is a barebones placeholder concrete implementation. DrawerComponent is the public wrapper that renders DrawerStandardComponent by default and accepts a custom replacement via DRAWER_STANDARD_COMPONENT_TOKEN.
Usage
<button type="button" (click)="open.set(true)">View cart</button>
<smart-drawer
[(open)]="open"
title="Shopping cart"
[options]="options"
(closed)="onClosed()"
>
<ul>
<li>Throwback Hip Bag, $90.00</li>
<li>Medium Stuff Satchel, $32.00</li>
</ul>
</smart-drawer>
Components
DrawerComponent (<smart-drawer>)
Main wrapper component. Renders DrawerStandardComponent by default. When DRAWER_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet.
DrawerStandardComponent (<smart-drawer-standard>)
Barebones placeholder concrete implementation. When open() is true, renders an optional overlay <div class="drawer-overlay"> (when options.withOverlay) and an <aside role="dialog" aria-modal="true"> with a data-position attribute (left or right, default right). When a title is provided, the aside includes a <header> with an <h2 id="smart-drawer-title"> and a close <button aria-label="Close">. Projected content is rendered via <ng-content />. It does not include Tailwind UI styling — it exists solely as the default structural placeholder until a custom implementation is registered through the token.
DrawerPresetComponent (<smart-drawer-preset>)
Fully-styled variation that extends DrawerBaseComponent and is a drop-in replacement for DrawerStandardComponent. Register it via DRAWER_STANDARD_COMPONENT_TOKEN to restyle every <smart-drawer>, or use the <smart-drawer-preset> selector directly. It renders the translated Preline offcanvas look: a fixed, sliding side panel (role="dialog", aria-modal="true", tabindex="-1") with a header (title + circular close button with the Preline X icon), a <ng-content /> body, and an optional dimmed backdrop. It consumes options.position (left/right, default right → data-position + start/end placement), options.wide (max-w-xs → max-w-md), options.withOverlay (renders a click-to-close backdrop), and options.brandedHeader (blue header bar with inverted title/close styling). Open/close is Angular-driven via the open model + @if and close() (no Preline JS runtime). All classes are smart:-prefixed Tailwind with explicit dark: variants. The class recipes live in preset/preset-classes.util.ts (getDrawerPanelClasses, getDrawerBackdropClasses, getDrawerHeaderClasses, getDrawerTitleClasses, getDrawerCloseClasses, getDrawerBodyClasses).
Because
DrawerComponentrenders injected components viaNgComponentOutlet(which passes inputs by canonical name),DrawerPresetComponentoverridescssClassasinput<string>('')without theclassalias. Bind it as[cssClass]when using the<smart-drawer-preset>selector directly, or just passclasson<smart-drawer>(the wrapper forwards it).The preset uses
<ng-content />for its body, which works with the<smart-drawer-preset>selector directly but is not propagated when rendered throughDRAWER_STANDARD_COMPONENT_TOKEN(see "Content Projection Limitation"). It does not consumeoptions.stickyFooter(no footer slot in the base API) oroptions.variant(content-type variants are projected via<ng-content />, not built into the offcanvas shell). Top/bottom placements from the Preline reference are not expressible —IDrawerOptions.positionisleft | rightonly.
DrawerBaseComponent (abstract)
Abstract base directive for extending custom drawer implementations. Exposes open as a two-way ModelSignal<boolean> (default false), title as an InputSignal<string | undefined>, options as an InputSignal<IDrawerOptions | undefined>, cssClass as an InputSignal<string> (with alias class), a closed output, and a close() method that sets open to false and emits closed.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
open | ModelSignal<boolean> | false | Whether the drawer is open (two-way bindable) |
title | InputSignal<string | undefined> | - | Optional drawer title (renders header when set) |
options | InputSignal<IDrawerOptions | undefined> | - | Optional configuration |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
Outputs
| Output | Type | Description |
|---|---|---|
closed | output<void> | Emitted when the drawer is closed (programmatic or button) |
IDrawerOptions
The standard component only consumes position (mapped to the data-position attribute on the aside, default 'right') and withOverlay (toggles the backdrop overlay). The remaining properties — wide, brandedHeader, stickyFooter, and variant — are reserved for custom implementations registered through DRAWER_STANDARD_COMPONENT_TOKEN and are ignored by DrawerStandardComponent.
DRAWER_STANDARD_COMPONENT_TOKEN
InjectionToken that allows replacing the default DrawerStandardComponent with a custom implementation. Provide a Type<DrawerBaseComponent> to override.
Extending the base class
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import {
DRAWER_STANDARD_COMPONENT_TOKEN,
DrawerBaseComponent,
DrawerComponent,
IDrawerOptions,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-drawer',
template: `
@if (open()) {
@if (options()?.withOverlay) {
<div class="docs-drawer__overlay" (click)="close()"></div>
}
<aside
role="dialog"
aria-modal="true"
[class]="panelClasses()"
[attr.data-position]="options()?.position ?? 'right'"
>
@if (title()) {
<header class="docs-drawer__header">
<h2>{{ title() }}</h2>
<button
type="button"
class="docs-drawer__close"
aria-label="Close"
(click)="close()"
>
×
</button>
</header>
}
<!--
smart-drawer renders a custom implementation through NgComponentOutlet,
which forwards neither projected content nor extra inputs. Only open,
title, options and cssClass arrive here, so a custom drawer renders its
own body instead of relying on ng-content.
-->
<p class="docs-drawer__body">Your cart is empty.</p>
</aside>
}
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomDrawerComponent extends DrawerBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
panelClasses = computed(() => {
const classes = ['docs-drawer__panel'];
if (this.options()?.wide) classes.push('docs-drawer__panel--wide');
const extra = this.cssClass();
if (extra) classes.push(extra);
return classes.join(' ');
});
}
@Component({
selector: 'docs-drawer-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [DrawerComponent],
// The token swaps the standard drawer for the custom one everywhere below
// this component, so consumers keep writing `<smart-drawer>`.
providers: [
{
provide: DRAWER_STANDARD_COMPONENT_TOKEN,
useValue: CustomDrawerComponent,
},
],
template: `
<smart-drawer [open]="true" title="Shopping cart" [options]="options" />
`,
})
export class DrawerCustomExampleComponent {
options: IDrawerOptions = { position: 'right', withOverlay: true };
}
Accessibility
- The aside is rendered with
role="dialog"andaria-modal="true"so assistive technologies treat it as a modal dialog. - When a
titleis provided, the aside is labelled viaaria-labelledby="smart-drawer-title"(matching the<h2 id="smart-drawer-title">inside the header). Without a title, no label is set — supply one throughclass-scoped styling or wrap the drawer in a labelled landmark if needed. - The header close button has
aria-label="Close". - Positioning is exposed declaratively via the
data-position="left|right"attribute on the aside, allowing CSS to style left- vs right-anchored drawers without runtime branching. Default isright. - The overlay (when
options.withOverlayistrue) closes the drawer on click.
Source
The component lives in packages/shared/angular/src/lib/components/drawer and is documented for Claude Code by the angular-components-drawer skill.