# LivaPwaInstall
Package: @dataliva/livalib
Language: tr
Description: @dataliva/livalib \"Uygulamayı Yükle\" (PWA kurulum) teklifi bileşeni ve useLivaPwaInstall hook'u.

Web uygulamasını ana ekrana kurmayı teklif eden banner ve tarayıcının kurulum
yeteneğini tek bir duruma indiren `useLivaPwaInstall` hook'u. Chromium tabanlı
tarayıcılarda (Android Chrome, Edge, masaüstü Chrome) "Yükle" butonu
tarayıcının yerel kurulum diyaloğunu açar. iOS'ta böyle bir diyalog olmadığı
için Paylaş menüsü adımlarını anlatan bir pencere gösterilir. Uygulama ana
ekrandan (standalone) açıldıysa bileşen hiçbir şey çizmez.

```tsx
import { LivaPwaInstall, useLivaPwaInstall } from "@dataliva/livalib";
```

## Temel kullanım

Bileşen uygulamada bir kez, teklifin görünmesini istediğiniz yere (ör. giriş
sayfası ya da kök layout) eklenir. `document.body` içine portal ile, ekranın
altına (`position="top"` ile üstüne) sabitlenmiş olarak çizilir. Telefonda
kenardan kenara bir çubuk, 576px ve üzeri genişlikte sağ köşeye oturan en
fazla 420px'lik bir kart olur; 400px'ten dar ekranlarda açıklama gizlenir ve
buton tam genişliğe iner. Alt/üst boşluk iOS'un güvenli alanını
(`safe-area-inset`) hesaba katar.

```tsx
import { LivaPwaInstall } from "@dataliva/livalib";
import logo from "./assets/logo.png";

<LivaPwaInstall appName="LivaBudget" icon={<img src={logo} alt="LivaBudget" />} />
```

Bu sitede çalışan bir banner örneği yoktur: dokümantasyon sitesinin web app
manifest'i olmadığı için tarayıcı kurulum olayını hiç tetiklemez ve bileşen
(iOS dışında) görünmez. Bileşen önizleme amaçlı zorla gösterilemez; aşağıdaki
canlı örnek bu yüzden hook'un döndürdüğü durumu gösterir.

## Banner ne zaman görünür

Banner, `delayMs` (varsayılan 1500 ms) dolduktan sonra ve aşağıdaki koşulların
hepsi sağlandığında çizilir (`shouldOffer`):

- **Kurulu değil** — bu oturumda `appinstalled` olayı görülmedi ve sayfa
  kurulu kabuk içinde açılmadı (`display-mode` `standalone`, `fullscreen` ya da
  `minimal-ui` değil; iOS'ta `navigator.standalone` değil; `android-app://`
  yönlendiricisinden gelinmedi).
- **Ertelenmemiş** — kullanıcı kapat (×) butonuna basmadı ya da basalı
  `remindAfterDays` gün geçti.
- **Platform listede** — cihaz `platforms` listesindeki bir platform. Varsayılan
  `['ios', 'android']` olduğu için **masaüstünde varsayılan olarak görünmez.**
- **Kurulum yolu var** — tarayıcı `beforeinstallprompt` olayını tetikledi
  (`isPromptAvailable`) ya da cihaz iOS ve adımların anlatılması gerekiyor
  (`needsManualSteps`). iOS'ta uygulama içi tarayıcılarda (Facebook,
  Instagram, Line, Twitter, WeChat) `needsManualSteps` `false` olduğundan
  teklif gösterilmez.

Platform `userAgent`'tan belirlenir; kendini masaüstü Safari gibi tanıtan
iPadOS 13+ dokunma noktası sayısından ayırt edilip `ios` sayılır.

### Butonların davranışı

- **Yükle** — Tarayıcı olayı yakalanmışsa yerel diyaloğu açar ve kullanıcının
  seçimini (`'accepted'` / `'dismissed'`) döner. Olay tek kullanımlıktır;
  diyalog kapandıktan sonra banner, tarayıcı olayı yeniden tetikleyene kadar
  (genellikle bir sonraki sayfa yüklemesi) kaybolur. Kurulum kabul edilirse
  `isInstalled` `true` olur. iOS'ta sonuç `'manual'` olur ve adım penceresi
  açılır; banner açık kalır. Diğer durumlarda `'unavailable'` döner. Sonuç
  `onInstall(result)` ile bildirilir.
- **Kapat (×)** — Teklifi erteler: zaman damgası `storageKey` anahtarıyla
  `localStorage`'a yazılır ve teklif `remindAfterDays` gün sonra yeniden
  görünür (`0` = bir daha sorma). Ardından `onDismiss` çağrılır. Tarayıcının
  yerel diyaloğundaki "İptal" erteleme sayılmaz. `localStorage`
  erişilemiyorsa (gizli sekme, engellenmiş depolama) erteleme yalnızca sayfa
  açık kaldığı sürece geçerli olur; sonraki açılışta teklif yeniden görünür.

## Türkçe metinler ve sade görünüm

Bileşenin yerleşik metinleri İngilizcedir (`Install Liva`, `Install App`,
`Not now`…); Türkçe arayüzde `i18n` ile verilmelidir. `appName` yalnızca
`i18n.title` verilmediğinde başlıkta (`Install <appName>`) kullanılır.

`compact` banner'da yalnızca simge ile kurulum butonunu bırakır; başlık ve
açıklama çizilmez, başlık banner'ın erişilebilirlik etiketi olarak kalır.
iOS adım metinlerindeki `{icon}` belirteci ilgili simgeyle (Paylaş, Ana
Ekrana Ekle, Ekle) değiştirilir; çeviride simgenin cümledeki yeri bu sayede
kaydırılabilir. Adım penceresinin başlığı yalnızca `instructionsTitle`
verildiğinde çizilir. LivaBudget giriş sayfası bileşeni şöyle kullanır:

```tsx
<LivaPwaInstall
  appName="LivaBudget"
  icon={<img src={logoIcon} alt="LivaBudget" />}
  storageKey="livabudget-pwa-install-dismissed"
  remindAfterDays={14}
  compact
  i18n={{
    // compact modda çizilmez; banner'ın erişilebilirlik etiketi olur
    title: "LivaBudget'ı Yükle",
    installText: "Uygulamayı Yükle",
    dismissText: "Şimdi değil",
    instructionsTitle: "Ana ekrana eklemek için",
    iosStep1: "Safari'de alttaki {icon} Paylaş butonuna dokunun",
    iosStep2: '"Ana Ekrana Ekle" seçeneğine dokunun',
    iosStep3: 'Sağ üstteki "Ekle" butonuna dokunun',
    gotItText: "Anladım",
  }}
/>
```

Aynı origin'de birden fazla uygulama çalışıyorsa her birine ayrı bir
`storageKey` verin; aksi hâlde birinde yapılan erteleme diğerini de susturur.

Not: Adım metinleri iOS tarayıcısına göre değişmez (varsayılan metin
Safari'yi anlatır). Chrome ya da Firefox için farklı metin göstermek
isterseniz hook'un döndürdüğü `iosBrowser` değerine göre `i18n`'i kendiniz
seçebilirsiniz.

## Masaüstünde de teklif etmek

`platforms` listesine `'desktop'` eklenirse teklif, kurulabilir durumdaki
masaüstü Chromium tarayıcılarında da görünür.

```tsx
<LivaPwaInstall
  appName="LivaBudget"
  platforms={["ios", "android", "desktop"]}
  position="top"
  delayMs={3000}
  onInstall={(result) => console.log("PWA kurulum sonucu:", result)}
  onDismiss={() => console.log("Kurulum teklifi ertelendi")}
/>
```

## useLivaPwaInstall

Banner'ın kullandığı durum hook'u; kendi arayüzünüzü (ör. kullanıcı menüsünde
kalıcı bir "Uygulamayı yükle" satırı) kurmak için doğrudan kullanılabilir.
Aşağıdaki örnek hook'u `'desktop'` dahil tüm platformlarla çağırır ve şu anki
tarayıcınızın değerlerini gösterir. Bu sitede manifest olmadığı için
masaüstünde ve Android'de `isPromptAvailable` `false`'tur ve `install()`
`'unavailable'` döner; iPhone/iPad Safari'de ise `needsManualSteps` `true` olur
ve `install()` `'manual'` döner. `dismiss()` ve `resetDismissal()` erteleme
kaydını yazar/siler; `isDismissed` ve `shouldOffer` anında güncellenir.

**Örnek — useLivaPwaInstall — bu tarayıcıdaki durum:**

```tsx
const pwa = useLivaPwaInstall({
  storageKey: "livadocs-pwa-install-demo",
  platforms: ["ios", "android", "desktop"],
});
const [sonuc, setSonuc] = useState<LivaPwaInstallResult | "—">("—");

const satirlar: [string, string][] = [
  ["platform", pwa.platform],
  ["iosBrowser", pwa.iosBrowser],
  ["isStandalone", String(pwa.isStandalone)],
  ["isInstalled", String(pwa.isInstalled)],
  ["isPromptAvailable", String(pwa.isPromptAvailable)],
  ["needsManualSteps", String(pwa.needsManualSteps)],
  ["isDismissed", String(pwa.isDismissed)],
  ["shouldOffer", String(pwa.shouldOffer)],
];

<table>
  <tbody>
    {satirlar.map(([ad, deger]) => (
      <tr key={ad}>
        <td><code>{ad}</code></td>
        <td>{deger}</td>
      </tr>
    ))}
  </tbody>
</table>
<LivaButton text="install()" onClick={async () => setSonuc(await pwa.install())} />
<LivaButton text="dismiss()" variant="outlined" onClick={pwa.dismiss} />
<LivaButton text="resetDismissal()" variant="text" onClick={pwa.resetDismissal} />
<span>Son install() sonucu: {sonuc}</span>
```

`shouldOffer` erteleme ve platform koşullarını da içerir. Kullanıcının
ertelemesinden etkilenmemesi gereken kalıcı bir menü öğesi için tekil
alanları kullanın:

```tsx
import { useLivaPwaInstall } from "@dataliva/livalib";

function InstallMenuItem({ onShowIosSteps }: { onShowIosSteps: () => void }) {
  const pwa = useLivaPwaInstall();

  if (pwa.isInstalled || (!pwa.isPromptAvailable && !pwa.needsManualSteps)) return null;

  return (
    <button
      type="button"
      onClick={async () => {
        const result = await pwa.install();
        if (result === "manual") onShowIosSteps(); // iOS: adımları kendi pencerenizde anlatın
      }}
    >
      Uygulamayı yükle
    </button>
  );
}
```

`platforms` seçeneği yalnızca `shouldOffer`'ı etkiler; `isPromptAvailable`,
`needsManualSteps` ve `isInstalled` her platformda doğru değeri taşır.

## Kurulum olayının yakalanması

`beforeinstallprompt` sayfa başına bir kez ve erken tetiklenir; React ağacı
kurulana kadar beklenirse kaçar. Bu yüzden livalib, modül yüklenir yüklenmez
`window`'a dinleyici bağlar (`primeLivaPwaInstall`), olayı saklar ve
`preventDefault()` ile tarayıcının kendi otomatik kurulum teklifini bastırır.
Durum modül düzeyinde tutulduğu için aynı sayfadaki tüm hook örnekleri ve
banner aynı olayı paylaşır; `appinstalled` olayı hepsinde `isInstalled`'ı
`true` yapar.

Önemli: Dinleyici `LivaPwaInstall` kullanılmasa bile livalib import edildiği
anda bağlanır. Yani livalib kullanan her uygulamada tarayıcının otomatik
kurulum teklifi (Android Chrome'daki mini bilgi çubuğu) görünmez; kurulum
teklifi bu bileşen ya da hook ile sunulmalıdır.

livalib geç yüklenen bir chunk içindeyse olay chunk yüklenmeden tetiklenip
kaçabilir. Bu durumda `primeLivaPwaInstall()`'ı uygulamanın giriş dosyasında
erkenden çağırın; birden fazla çağrı güvenlidir:

```tsx
// main.tsx
import { primeLivaPwaInstall } from "@dataliva/livalib";

primeLivaPwaInstall();
```

## Uygulama tarafında gerekenler

Bileşen manifest üretmez ve service worker kaydetmez; yalnızca tarayıcının
verdiği kurulum olayını yönetir. Chromium'un `beforeinstallprompt` olayını
tetiklemesi için uygulamanın tarayıcının kurulabilirlik ölçütlerini karşılaması
gerekir: HTTPS (geliştirmede `localhost`) üzerinden sunulmalı ve `name` /
`short_name`, `icons`, `start_url` ile `display` alanlarını içeren bir web app
manifest'i bulunmalıdır. LivaBudget'ta manifest ve service worker
`vite-plugin-pwa` ile üretilir.

Manifest'teki `display` değerini `standalone` (ya da `fullscreen` /
`minimal-ui`) yapın: bileşen kurulu açılışı bu `display-mode` medya
sorgularıyla tanır ve ana ekrandan açılan uygulamada teklifi göstermez.

## Prop'lar

### LivaPwaInstall

| Prop | Tip | Varsayılan |
|------|------|---------|
| `appName` | `string` | `'Liva'` |
| `icon` | `ReactNode` | — (verilmezse simge alanı çizilmez) |
| `i18n` | `LivaPwaInstallI18n` | — (İngilizce metinler) |
| `storageKey` | `string` | `'liva-pwa-install-dismissed'` |
| `remindAfterDays` | `number` | `7` (`0` = bir daha sorma) |
| `platforms` | `LivaPwaPlatform[]` | `['ios', 'android']` |
| `delayMs` | `number` | `1500` (`0` ya da altı = hemen) |
| `position` | `'bottom' \| 'top'` | `'bottom'` |
| `compact` | `boolean` | `false` |
| `onInstall` | `(result: LivaPwaInstallResult) => void` | — |
| `onDismiss` | `() => void` | — |
| `className` | `string` | `''` |

### LivaPwaInstallI18n

| Alan | Kullanıldığı yer | Varsayılan |
|------|------|---------|
| `title` | Banner başlığı ve erişilebilirlik etiketi | `` `Install ${appName}` `` |
| `description` | Banner açıklaması | `'Add it to your home screen and open it like a native app.'` |
| `installText` | Kurulum butonu | `'Install App'` |
| `dismissText` | Kapat butonunun etiketi ve ipucu | `'Not now'` |
| `instructionsTitle` | iOS adım penceresinin başlığı | — (verilmezse başlık çizilmez) |
| `iosStep1` | 1. adım (`{icon}` = Paylaş simgesi) | `'Tap the {icon} Share button in the bottom bar of Safari'` |
| `iosStep2` | 2. adım (`{icon}` = Ana Ekrana Ekle simgesi) | `'Choose "Add to Home Screen"'` |
| `iosStep3` | 3. adım (`{icon}` = Ekle simgesi) | `'Tap "Add" at the top right'` |
| `gotItText` | Adım penceresini kapatan buton | `'Got it'` |

### useLivaPwaInstall seçenekleri

| Alan | Tip | Varsayılan |
|------|------|---------|
| `storageKey` | `string` | `'liva-pwa-install-dismissed'` |
| `remindAfterDays` | `number` | `7` |
| `platforms` | `LivaPwaPlatform[]` | `['ios', 'android']` |

### useLivaPwaInstall — dönen değer (LivaPwaInstallState)

| Alan | Tip | Açıklama |
|------|------|---------|
| `platform` | `LivaPwaPlatform` | `'ios'`, `'android'` ya da `'desktop'`. |
| `iosBrowser` | `LivaPwaIosBrowser` | iOS'taki tarayıcı: `'safari'`, `'chrome'`, `'firefox'`, `'edge'`, `'in-app'`. iOS dışında da hesaplanır (genellikle `'safari'`). |
| `isStandalone` | `boolean` | Uygulama ana ekrandan / kurulu kabuktan açılmış. |
| `isInstalled` | `boolean` | Bu oturumda kurulum tamamlandı ya da zaten kabuk içindeyiz. |
| `isPromptAvailable` | `boolean` | Tarayıcının yerel kurulum diyaloğu açılabilir. |
| `needsManualSteps` | `boolean` | iOS, yerel diyalog yok ve uygulama içi tarayıcı değil — adımlar anlatılmalı. |
| `isDismissed` | `boolean` | Teklif ertelendi ve süre dolmadı. |
| `shouldOffer` | `boolean` | Teklif gösterilmeli mi — yukarıdakilerin ve `platforms`'un bileşkesi. |
| `install` | `() => Promise<LivaPwaInstallResult>` | Yerel diyaloğu açar; diyalog yoksa iOS'ta `'manual'`, diğerlerinde `'unavailable'` döner. |
| `dismiss` | `() => void` | Teklifi erteler (süre `remindAfterDays`). |
| `resetDismissal` | `() => void` | Erteleme kaydını siler. |

### Tipler ve yardımcılar

| Ad | Değer |
|------|------|
| `LivaPwaPlatform` | `'ios' \| 'android' \| 'desktop'` |
| `LivaPwaIosBrowser` | `'safari' \| 'chrome' \| 'firefox' \| 'edge' \| 'in-app'` |
| `LivaPwaInstallResult` | `'accepted' \| 'dismissed' \| 'manual' \| 'unavailable'` |
| `LivaBeforeInstallPromptEvent` | Chromium'un standart dışı `beforeinstallprompt` olayı: `platforms`, `userChoice`, `prompt()` |
| `primeLivaPwaInstall` | `() => void` — olay dinleyicisini bağlar; modül yüklenirken kendiliğinden çağrılır, tekrar çağrı güvenlidir. |

Ayrıca `LivaPwaInstallProps`, `UseLivaPwaInstallOptions` ve
`LivaPwaInstallState` tipleri dışa açıktır.
