# `@a.nemreen/dga-ui`

<p align="center">
  <img src="https://cdn.jsdelivr.net/npm/@a.nemreen/dga-ui@0.2.1/cover.jpg" alt="DGA Angular — design tokens & UI" width="100%" />
</p>

[![npm version](https://img.shields.io/npm/v/@a.nemreen/dga-ui.svg)](https://www.npmjs.com/package/@a.nemreen/dga-ui)
[![license](https://img.shields.io/npm/l/@a.nemreen/dga-ui.svg)](https://github.com/nemreen/dga-angular/blob/main/LICENSE)
[![Angular](https://img.shields.io/badge/Angular-19-DD0031?logo=angular&logoColor=white)](https://angular.dev)
[![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-v4-38B2AC?logo=tailwindcss&logoColor=white)](https://tailwindcss.com)
[![Live demo](https://img.shields.io/badge/demo-nemreen.info%2Fdga-1B8354)](https://nemreen.info/dga/)

Standalone Angular UI components for the DGA design system — forms, overlays, navigation, portal shell, and layout. Styled with Tailwind utilities mapped to [`@a.nemreen/dga-tokens`](https://www.npmjs.com/package/@a.nemreen/dga-tokens).

**Live demo:** [nemreen.info/dga](https://nemreen.info/dga/)

> Version **0.3.x** until public API freeze (1.0).

## Changelog

[CHANGELOG.md](https://github.com/nemreen/dga-angular/blob/main/CHANGELOG.md) ships with this package. The same notes, in Arabic and English, are at [nemreen.info/dga/docs/changelog](https://nemreen.info/dga/docs/changelog).

## Features

- Standalone Angular 19+ components (no NgModules)
- RTL-first / Arabic-ready (logical CSS + locale-aware chrome)
- Forms: input, select, autocomplete (async/creatable), checkbox, radio, switch, OTP, upload, date/time picker, signature pad, rich text, …
- Overlays: dialog, modal, drawer, bottom sheet, popover, toast, menu, tooltip, command
- Shell: header, top bar, hero, footer, preloader, theme toggle, locale toggle
- Feedback & a11y: alert, cookies, skip link, notification inbox (accessibility panel → `@a.nemreen/a11y`)
- Data: table (templates, filters, expand, bulk), virtual list, export, carousel, chart
- Content: timeline, calendar events, FAQ, QR/barcode, metrics
- Layout: card, section, grid, clamp, expandable, app shell

## Install

```bash
npm install @a.nemreen/dga-ui @a.nemreen/dga-tokens
```

**Peers:** `@angular/core` / `@angular/common` / `@angular/forms` / `@angular/platform-browser` ^19–22, `@a.nemreen/dga-tokens` ^0.2 || ^0.3.

## Library layout

Sources live under `src/lib/<domain>/<component>/`. Place new work in the matching domain — do not add new top-level peers under `lib/`.

| Domain | Use for |
|--------|---------|
| `primitives/` | Visual atoms (button, icon, badge, …) |
| `forms/` | Inputs, selects, field chrome |
| `overlays/` | Floating UI (dialog, toast, menu, …) |
| `feedback/` | Status and user-response UI (alert, cookies, a11y, …) |
| `navigation/` | Wayfinding (tabs, breadcrumb, side-nav, …) |
| `layout/` | Structure wrappers (card, section, grid, …) |
| `shell/` | Page chrome (header, footer, hero, …) |
| `content/` | Content presentation (list, quote, metric, …) |
| `data/` | Tables, charts, export |
| `domain/` | Locale/region data helpers (not UI). Saudi cities ship as `@a.nemreen/dga-ui/saudi-cities`. |
| `utils/` | Internal helpers only (not exported) |

Import from the public package surface — do not deep-import `src/lib/...` paths.

New `dga-*` custom elements must also be listed in `@a.nemreen/dga-tokens` `styles/host-compat.css` (`:is(...)`) so host-app `*` / `a` resets cannot flatten them.

### Secondary entry points

Heavier shell pieces ship as separate FESMs so the primary bundle stays lean:

```ts
import { DgaHeader } from '@a.nemreen/dga-ui/header';
import { DgaCookies } from '@a.nemreen/dga-ui/cookies';
import { DgaDatePicker } from '@a.nemreen/dga-ui/date-picker';
import { DgaChart } from '@a.nemreen/dga-ui/chart';
import { DgaChatbot } from '@a.nemreen/dga-ui/chatbot';
import { DgaPageTransition } from '@a.nemreen/dga-ui/page-transition';
import { DGA_SAUDI_CITIES } from '@a.nemreen/dga-ui/saudi-cities';
```

Accessibility tools live in a separate package — [`@a.nemreen/a11y`](https://www.npmjs.com/package/@a.nemreen/a11y) — mount with `Accessibility.mount()`.

## Setup

```css
@import 'tailwindcss';
@import '@a.nemreen/dga-tokens/styles.css';
```

Keep existing app resets (`* { margin: 0; padding: 0 }`, `a { color: var(--color-mainColor) }`). Tokens ship unlayered `host-compat.css` (last in `styles.css`) so DGA components win those globals. New `dga-*` tags must be added to that `:is()` list. Optional: wrap leftover host CSS in `@layer base` so *your* Tailwind utilities also win — see the tokens README.

Load Arabic Google Fonts (IBM Plex default; see `@a.nemreen/dga-tokens` README / `DgaFontService`).

Scan library sources so Tailwind keeps component utilities (path may vary):

```css
@source "../node_modules/@a.nemreen/dga-ui/**/*.{mjs,js}";
```

```ts
import {
  DgaButton,
  DgaInput,
  DgaLabel,
  DgaBadge,
  DgaAlert,
  DgaCheckbox,
  DgaRadio,
  DgaSelect,
  DgaNativeSelect,
  DgaField,
  DgaSpinner,
  DgaDialog,
  DgaToast,
  DgaCard,
  DgaDivider,
  DgaIcon,
  DgaTable,
  DgaBottomSheet,
  DgaPopover,
  DgaThemeToggle,
  DgaLocaleToggle,
} from '@a.nemreen/dga-ui';
```

## Components

### Icon

Two layers, both HugeIcons Stroke Rounded (DGA / Platforms Code family):

1. **Chrome** — curated inline SVG via `name` (paints with the first frame; use in buttons, fields, menus).
2. **Content** — optional font via `glyph` (opt into `@a.nemreen/dga-tokens/styles/icon-font.css`).

```html
<!-- Chrome (always available) -->
<dga-icon name="search" size="sm" />
<dga-icon name="arrowNext" size="sm" label="التالي" />
<dga-icon name="riyal" size="md" />

<!-- Content (requires icon-font.css) -->
<dga-icon glyph="search-01" size="xl" />
<dga-icon glyph="notification-02" size="xl" tone="primary" />
```

Use `arrowNext` / `arrowPrev` for forward/back — they mirror under `dir="rtl"`. Prefer literal `arrowLeft` / `arrowRight` only when you mean that physical direction.

Sizes (px): `2xs` 10 · `xs` 14 · `sm` 16 · `md` 18 · `lg` 20 · `xl` 24 · `2xl` 28 · `3xl` 32.

`DgaIcon` inlines a small HugeIcons core map (close / menu / search / chevrons / check / user / …) and lazy-loads the curated catalog on the first non-core `name`. Apps can add icons without forking the library:

```ts
import { provideDgaIcons } from '@a.nemreen/dga-ui';

bootstrapApplication(App, {
  providers: [
    provideDgaIcons({
      myBrand: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>`,
    }),
  ],
});
```

```html
<dga-icon name="myBrand" size="md" />
```

Source: `@hugeicons/core-free-icons` (same family as design.dga.gov.sa). Regenerate with `node scripts/generate-dga-icons.mjs`.

### Scroll to top

Floating control that appears after the page scrolls past a threshold. Mount once at the app root; it stacks above `@a.nemreen/a11y` by default.

```html
<dga-scroll-top position="end" [threshold]="320" />
```

### Button

```html
<button dgaButton type="button">حفظ</button>
<button dgaButton variant="outline" size="sm">إلغاء</button>
<button dgaButton variant="danger" disabled>حذف</button>
```

### Input + Label + Field

```html
<label dgaLabel dgaRequired for="email">البريد</label>
<input dgaInput id="email" type="email" />
<textarea dgaInput rows="3"></textarea>

<dga-field label="البريد" controlId="email" required hint="لن نشارك بريدك.">
  <input dgaInput id="email" type="email" />
</dga-field>
```

### Select

Configurable select (search, multi, always-open):

```html
<dga-select [options]="cities" [(value)]="city" />
<dga-select searchable [options]="cities" [(value)]="city" />
<dga-select multiple [options]="cities" [(value)]="cities" />
<dga-select multiple searchable [options]="cities" [(value)]="cities" />
<dga-select openMode="always" [options]="cities" [(value)]="city" />
```

Native `<select>` styling for simple forms:

```html
<select dgaSelect>
  <option value="riyadh">الرياض</option>
</select>
```

### Checkbox + Radio

```html
<label>
  <input dgaCheckbox type="checkbox" />
  أوافق
</label>

<label>
  <input dgaRadio type="radio" name="plan" value="basic" />
  أساسية
</label>
```

### Badge

```html
<dga-badge variant="success">مكتمل</dga-badge>
```

### Alert

```html
<dga-alert variant="warning" title="تنبيه">تحقق من البيانات.</dga-alert>
<dga-alert dismissible (dismissed)="onDismiss()">يمكن إغلاقه</dga-alert>
```

### Spinner

```html
<dga-spinner />
<dga-spinner size="lg" tone="neutral" />
```

### Preloader (optional first-load)

Full-screen brand overlay; auto-hides when the document is ready and `minDuration` elapses. Omit the tag if you do not want it.

```html
<dga-preloader
  brand="بوابة الخدمات"
  label="جاري التحميل"
  [minDuration]="1000"
  [once]="true"
  persist="local"
/>
```

- `once` + `persist="local"` - show only on the visitor’s first visit  
- `persist="session"` - once per tab session  
- `persist="none"` or `[once]="false"` - every load  
- Optional `logoSrc` replaces the default geometric mark  
- `(closed)` fires after the exit animation  

```ts
import { DgaPreloader } from '@a.nemreen/dga-ui';
```

### Dialog

```html
<dga-dialog [(open)]="open" title="تأكيد" animation="scale">
  <p>هل أنت متأكد؟</p>
  <div dgaDialogActions>
    <button dgaButton variant="ghost" (click)="open = false">إلغاء</button>
    <button dgaButton (click)="confirm()">تأكيد</button>
  </div>
</dga-dialog>
```

`animation`: `scale` (default, fade and grow), `fade`, `slide` (rise), or `none`. `prefers-reduced-motion` uses `none`. `(closed)` fires after the exit animation, and only for Escape, the backdrop, or the close button.

### Modal

```html
<dga-modal
  [(open)]="open"
  title="تفاصيل الطلب"
  description="راجع البيانات قبل المتابعة"
  size="lg"
  animation="scale"
>
  <p>محتوى النافذة المنبثقة.</p>
  <div dgaModalActions>
    <button dgaButton variant="ghost" (click)="open = false">إلغاء</button>
    <button dgaButton (click)="confirm()">تأكيد</button>
  </div>
</dga-modal>
```

### Toast

```html
<dga-toast [(open)]="show" variant="success" title="تم">حُفظ السجل.</dga-toast>
```

### Tooltip / Switch / Progress / Nav (Wave I)

```html
<dga-switch [(checked)]="on" label="الإشعارات" />
<a dgaLink href="#">رابط</a>
<dga-progress [value]="60" />
<dga-skeleton />
<dga-skeleton [lines]="3" animation="shimmer" />
<dga-skeleton shape="circle" size="lg" />
<dga-tooltip title="مساعدة" text="وصف مختصر">
  <button dgaButton size="sm">؟</button>
</dga-tooltip>

<dga-breadcrumb [items]="[{ label: 'الرئيسية', href: '#' }, { label: 'الحالي' }]" />
<dga-tabs [(selected)]="tab">
  <dga-tab value="a" label="أ">...</dga-tab>
</dga-tabs>
<dga-content-switcher [(selected)]="view">
  <dga-content-switcher-panel value="all" label="الكل">...</dga-content-switcher-panel>
</dga-content-switcher>
<dga-accordion>
  <dga-accordion-item title="سؤال">جواب</dga-accordion-item>
</dga-accordion>
<dga-pagination [(page)]="page" [totalPages]="10" />
```

## Layout recipes

Prefer DGA spacing tokens (`gap-md`, `p-xl`, `py-3xl`) over raw pixel values. For page width, use `max-w-(--container-*)` - plain `max-w-5xl` binds to the spacing scale in Tailwind v4.

### Stack (column)

```html
<div class="flex flex-col gap-md">
  <p>صف واحد</p>
  <p>صف آخر</p>
</div>
```

### Inline row

```html
<div class="flex flex-wrap items-center gap-md">
  <button dgaButton>حفظ</button>
  <button dgaButton variant="ghost">إلغاء</button>
</div>
```

### Split (space between)

```html
<div class="flex items-center justify-between gap-xl">
  <h2 class="text-display-sm">العنوان</h2>
  <div class="flex gap-sm">…</div>
</div>
```

### Responsive grid

```html
<div class="grid grid-cols-1 gap-lg md:grid-cols-2 lg:grid-cols-3">
  <dga-card title="أ">…</dga-card>
  <dga-card title="ب">…</dga-card>
  <dga-card title="ج">…</dga-card>
</div>
```

Or use the thin wrapper:

```html
<dga-grid [cols]="3" gap="lg">
  <dga-card title="أ">…</dga-card>
  <dga-card title="ب">…</dga-card>
  <dga-card title="ج">…</dga-card>
</dga-grid>
```

### Contained section

```html
<dga-section title="القسم" subtitle="وصف قصير" contained>
  محتوى القسم
</dga-section>
```

### Portal home shell (consumable across apps)

Compose the official home template with library primitives only - no page-local CSS required:

```html
<dga-top-bar registrationId="00000000000" />

<dga-header
  appearance="portal"
  mobileNav="sheet"
  brand="شعار المنصة"
  brandSlogan="بوابة الجهة"
  brandHref="/"
  [items]="navItems"
  [actions]="headerActions"
  (actionClick)="onAction($event)"
/>
<!-- mobileNav: sheet | fullscreen | push -->

<dga-hero appearance="portal" [slides]="heroSlides" [(activeIndex)]="i" />

<dga-section
  width="wide"
  tone="surface"
  title="قسم من نحن"
  subtitle="…"
>
  <button dgaSectionActions dgaButton variant="outline">إجراء ثانوي</button>
  <dga-grid [cols]="4" gap="xl">
    <dga-metric appearance="stat" value="1.5M" label="شخص">
      <dga-icon-badge dgaMetricIcon>
        <dga-icon name="user" tone="primary" />
      </dga-icon-badge>
    </dga-metric>
  </dga-grid>
</dga-section>

<dga-card title="عنوان البطاقة" description="نص إضافي">
  <dga-icon-badge dgaCardLead>
    <dga-icon name="home" tone="primary" />
  </dga-icon-badge>
  <div dgaCardActions>
    <button dgaButton variant="outline" size="sm">اجراء ثانوي</button>
    <button dgaButton size="sm">اجراء</button>
  </div>
</dga-card>

<dga-card>
  <dga-card-media dgaCardMedia alt="خبر" />
  …
</dga-card>

<dga-logo-tile label="شريك" />
<dga-footer
  appearance="brand"
  [columns]="footerColumns"
  [iconGroups]="footerIconGroups"
  [bottomLinks]="footerBottomLinks"
  [logos]="footerLogos"
  copyright="جميع الحقوق محفوظة © 2026"
  meta="آخر تحديث للموقع في 11 سبتمبر 2026 7:30 ص"
/>
```

Portal tokens (layout sizes): `--size-hero-portal` (491px), `--size-header-portal` (72px), `--size-icon-badge` (40px), `--size-dot` (16px).

### Content + sticky aside

```html
<div class="mx-auto grid max-w-(--container-7xl) gap-3xl px-xl md:grid-cols-[1fr_auto]">
  <main class="min-w-0">…</main>
  <dga-side-info title="في هذه الصفحة" [items]="tocItems" />
</div>
```

## License

MIT — see [LICENSE](./LICENSE). Copyright (c) 2026 Ahmed Nemreen.

Use it in any project. **Do not rebrand and sell it as your own unique product.**
