tamga-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/blocks/count-row.d.ts +29 -0
- package/dist/blocks/count-row.d.ts.map +1 -0
- package/dist/blocks/count-row.js +19 -0
- package/dist/blocks/filter-bar.d.ts +123 -0
- package/dist/blocks/filter-bar.d.ts.map +1 -0
- package/dist/blocks/filter-bar.js +74 -0
- package/dist/blocks/index.d.ts +24 -0
- package/dist/blocks/index.d.ts.map +1 -0
- package/dist/blocks/index.js +23 -0
- package/dist/blocks/save-bar.d.ts +31 -0
- package/dist/blocks/save-bar.d.ts.map +1 -0
- package/dist/blocks/save-bar.js +21 -0
- package/dist/components/a11y.d.ts +21 -0
- package/dist/components/a11y.d.ts.map +1 -0
- package/dist/components/a11y.js +159 -0
- package/dist/components/advanced-input.d.ts +115 -0
- package/dist/components/advanced-input.d.ts.map +1 -0
- package/dist/components/advanced-input.js +189 -0
- package/dist/components/avatar.d.ts +63 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +54 -0
- package/dist/components/badge.d.ts +37 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +29 -0
- package/dist/components/bar.d.ts +32 -0
- package/dist/components/bar.d.ts.map +1 -0
- package/dist/components/bar.js +33 -0
- package/dist/components/button.d.ts +45 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +56 -0
- package/dist/components/chart.d.ts +43 -0
- package/dist/components/chart.d.ts.map +1 -0
- package/dist/components/chart.js +41 -0
- package/dist/components/checkbox.d.ts +13 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +16 -0
- package/dist/components/chrome.d.ts +87 -0
- package/dist/components/chrome.d.ts.map +1 -0
- package/dist/components/chrome.js +107 -0
- package/dist/components/combobox.d.ts +31 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.js +62 -0
- package/dist/components/control-base.d.ts +7 -0
- package/dist/components/control-base.d.ts.map +1 -0
- package/dist/components/control-base.js +6 -0
- package/dist/components/data-table.d.ts +68 -0
- package/dist/components/data-table.d.ts.map +1 -0
- package/dist/components/data-table.js +50 -0
- package/dist/components/date-picker.d.ts +52 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +73 -0
- package/dist/components/disclosure.d.ts +31 -0
- package/dist/components/disclosure.d.ts.map +1 -0
- package/dist/components/disclosure.js +32 -0
- package/dist/components/display.d.ts +188 -0
- package/dist/components/display.d.ts.map +1 -0
- package/dist/components/display.js +190 -0
- package/dist/components/empty-state.d.ts +150 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +113 -0
- package/dist/components/error-state.d.ts +38 -0
- package/dist/components/error-state.d.ts.map +1 -0
- package/dist/components/error-state.js +16 -0
- package/dist/components/file-upload.d.ts +34 -0
- package/dist/components/file-upload.d.ts.map +1 -0
- package/dist/components/file-upload.js +34 -0
- package/dist/components/gauge.d.ts +47 -0
- package/dist/components/gauge.d.ts.map +1 -0
- package/dist/components/gauge.js +65 -0
- package/dist/components/icon.d.ts +32 -0
- package/dist/components/icon.d.ts.map +1 -0
- package/dist/components/icon.js +20 -0
- package/dist/components/icons.d.ts +19 -0
- package/dist/components/icons.d.ts.map +1 -0
- package/dist/components/icons.js +39 -0
- package/dist/components/input.d.ts +35 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +41 -0
- package/dist/components/layout.d.ts +133 -0
- package/dist/components/layout.d.ts.map +1 -0
- package/dist/components/layout.js +132 -0
- package/dist/components/link.d.ts +22 -0
- package/dist/components/link.d.ts.map +1 -0
- package/dist/components/link.js +2 -0
- package/dist/components/live-scope.d.ts +15 -0
- package/dist/components/live-scope.d.ts.map +1 -0
- package/dist/components/live-scope.js +67 -0
- package/dist/components/log-view.d.ts +34 -0
- package/dist/components/log-view.d.ts.map +1 -0
- package/dist/components/log-view.js +30 -0
- package/dist/components/number-input.d.ts +26 -0
- package/dist/components/number-input.d.ts.map +1 -0
- package/dist/components/number-input.js +57 -0
- package/dist/components/overlay.d.ts +257 -0
- package/dist/components/overlay.d.ts.map +1 -0
- package/dist/components/overlay.js +276 -0
- package/dist/components/pagination.d.ts +38 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +46 -0
- package/dist/components/pie.d.ts +40 -0
- package/dist/components/pie.d.ts.map +1 -0
- package/dist/components/pie.js +74 -0
- package/dist/components/primitives.d.ts +96 -0
- package/dist/components/primitives.d.ts.map +1 -0
- package/dist/components/primitives.js +81 -0
- package/dist/components/progress.d.ts +29 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +16 -0
- package/dist/components/radio-group.d.ts +17 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +23 -0
- package/dist/components/rail-link.d.ts +24 -0
- package/dist/components/rail-link.d.ts.map +1 -0
- package/dist/components/rail-link.js +24 -0
- package/dist/components/rich-text.d.ts +51 -0
- package/dist/components/rich-text.d.ts.map +1 -0
- package/dist/components/rich-text.js +147 -0
- package/dist/components/section.d.ts +15 -0
- package/dist/components/section.d.ts.map +1 -0
- package/dist/components/section.js +8 -0
- package/dist/components/segmented.d.ts +13 -0
- package/dist/components/segmented.d.ts.map +1 -0
- package/dist/components/segmented.js +12 -0
- package/dist/components/skeleton.d.ts +69 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.js +67 -0
- package/dist/components/slider.d.ts +18 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +15 -0
- package/dist/components/sparkline.d.ts +17 -0
- package/dist/components/sparkline.d.ts.map +1 -0
- package/dist/components/sparkline.js +19 -0
- package/dist/components/spinner.d.ts +15 -0
- package/dist/components/spinner.d.ts.map +1 -0
- package/dist/components/spinner.js +22 -0
- package/dist/components/stacked-bar.d.ts +21 -0
- package/dist/components/stacked-bar.d.ts.map +1 -0
- package/dist/components/stacked-bar.js +41 -0
- package/dist/components/surface.d.ts +76 -0
- package/dist/components/surface.d.ts.map +1 -0
- package/dist/components/surface.js +49 -0
- package/dist/components/switch.d.ts +12 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +13 -0
- package/dist/components/tabs.d.ts +47 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +12 -0
- package/dist/components/timeline-strip.d.ts +19 -0
- package/dist/components/timeline-strip.d.ts.map +1 -0
- package/dist/components/timeline-strip.js +14 -0
- package/dist/components/tone.d.ts +58 -0
- package/dist/components/tone.d.ts.map +1 -0
- package/dist/components/tone.js +70 -0
- package/dist/components/tree-select.d.ts +26 -0
- package/dist/components/tree-select.d.ts.map +1 -0
- package/dist/components/tree-select.js +59 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/kit.css +2248 -0
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +12 -0
- package/dist/lib/color.d.ts +68 -0
- package/dist/lib/color.d.ts.map +1 -0
- package/dist/lib/color.js +134 -0
- package/dist/lib/palette.d.ts +84 -0
- package/dist/lib/palette.d.ts.map +1 -0
- package/dist/lib/palette.js +193 -0
- package/dist/lib/scroll-lock.d.ts +19 -0
- package/dist/lib/scroll-lock.d.ts.map +1 -0
- package/dist/lib/scroll-lock.js +33 -0
- package/dist/patterns/app-shell.d.ts +36 -0
- package/dist/patterns/app-shell.d.ts.map +1 -0
- package/dist/patterns/app-shell.js +14 -0
- package/dist/patterns/auth-template.d.ts +36 -0
- package/dist/patterns/auth-template.d.ts.map +1 -0
- package/dist/patterns/auth-template.js +40 -0
- package/dist/patterns/detail-template.d.ts +52 -0
- package/dist/patterns/detail-template.d.ts.map +1 -0
- package/dist/patterns/detail-template.js +11 -0
- package/dist/patterns/index.d.ts +18 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +17 -0
- package/dist/patterns/list-template.d.ts +32 -0
- package/dist/patterns/list-template.d.ts.map +1 -0
- package/dist/patterns/list-template.js +8 -0
- package/dist/patterns/overview-template.d.ts +26 -0
- package/dist/patterns/overview-template.d.ts.map +1 -0
- package/dist/patterns/overview-template.js +8 -0
- package/dist/patterns/public-template.d.ts +28 -0
- package/dist/patterns/public-template.d.ts.map +1 -0
- package/dist/patterns/public-template.js +7 -0
- package/dist/patterns/settings-template.d.ts +50 -0
- package/dist/patterns/settings-template.d.ts.map +1 -0
- package/dist/patterns/settings-template.js +33 -0
- package/dist/patterns/shared.d.ts +40 -0
- package/dist/patterns/shared.d.ts.map +1 -0
- package/dist/patterns/shared.js +29 -0
- package/dist/patterns/wizard-template.d.ts +28 -0
- package/dist/patterns/wizard-template.d.ts.map +1 -0
- package/dist/patterns/wizard-template.js +15 -0
- package/dist/styles.css +5 -0
- package/dist/theme.css +812 -0
- package/package.json +87 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Children, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Icon } from "./icon.js";
|
|
6
|
+
import { PlainLink as PlainAnchor } from "./link.js";
|
|
7
|
+
import { Check, Customize } from "./icons.js";
|
|
8
|
+
import { toneOf } from "./tone.js";
|
|
9
|
+
/* ------------------------------------------------------------------ *
|
|
10
|
+
* Okunacak şeyler: anahtar/değer, sayı, kod, adım, sayaç, tuş.
|
|
11
|
+
*
|
|
12
|
+
* Beşi de üründe HAM yazılmış hâlde duruyordu — `<dl>` beş dosyada, `<code>`
|
|
13
|
+
* sekiz dosyada, KPI karosu kendi 28 satırında. Yani tasarımları zaten
|
|
14
|
+
* verilmişti; eksik olan tek şey, o tasarımın bir kez yazılmış olmasıydı.
|
|
15
|
+
* ------------------------------------------------------------------ */
|
|
16
|
+
/**
|
|
17
|
+
* Anahtar/değer listesi — detay sayfalarının omurgası.
|
|
18
|
+
*
|
|
19
|
+
* Gerekçe: docs/gerekce/05-yuzey-ve-kabuk.md
|
|
20
|
+
*/
|
|
21
|
+
export function Descriptions({ items, layout = "wide", className, }) {
|
|
22
|
+
return (_jsx("dl", { className: cn("grid", layout === "wide"
|
|
23
|
+
? "gap-x-8 gap-y-4 sm:grid-cols-[minmax(0,14rem)_minmax(0,1fr)]"
|
|
24
|
+
: "gap-x-4 gap-y-2.5 sm:grid-cols-[minmax(0,8rem)_minmax(0,1fr)]", className), children: items.map((it) => (_jsxs("div", { className: "contents", children: [_jsx("dt", { className: "text-small text-ink-faint", children: it.term }), _jsx("dd", { className: cn("min-w-0 text-body text-ink", it.mono && "font-mono tabular-nums"), children: it.value })] }, it.term))) }));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Tek bir sayı, büyük. Sektörde adı "stat" ya da "KPI kartı".
|
|
28
|
+
*
|
|
29
|
+
* `SkeletonKpi` kitte YILLARCA vardı ve yerini tuttuğu bileşen yoktu — bir
|
|
30
|
+
* iskelet, var olmayan bir şeyin yerini tutuyordu. Kitin en tuhaf açığıydı.
|
|
31
|
+
*
|
|
32
|
+
* TIKLANABİLİR OLMASI SONRADAN GELDİ, ve gelmek zorundaydı. İlk hâli yalnız
|
|
33
|
+
* okunuyordu; oysa bir panodaki sayı neredeyse hiçbir zaman salt okunur
|
|
34
|
+
* değildir. Ölçüldü: bir ürün panelinde bu karo iki kez elle yeniden yazılmış,
|
|
35
|
+
* çünkü biri bir EKRAN AÇIYOR ("37 iade bekliyor" → iade listesi), öteki bir
|
|
36
|
+
* FİLTRE UYGULUYOR (aynı listeyi o adıma indiriyor). Kitin karosu ikisini de
|
|
37
|
+
* yapamadığı için iki kopya doğdu ve ikisi birbirine benzemez oldu. Yeteneği
|
|
38
|
+
* eksik bir bileşen, olmayan bir bileşenden daha çok kopya üretiyor.
|
|
39
|
+
*
|
|
40
|
+
* ÜÇ HÂL, VE SEMANTİK ELEMAN HER BİRİNDE FARKLI:
|
|
41
|
+
*
|
|
42
|
+
* okunur `<div>` — sayı bir bilgi
|
|
43
|
+
* `href` `<a>` — sayı bir yere gidiyor
|
|
44
|
+
* `onClick` `<button>` — sayı bir şeyi değiştiriyor, `pressed` ile açık/kapalı
|
|
45
|
+
*
|
|
46
|
+
* Üçünü tek elemanla yapmak (her şey `<div onClick>`) klavyeyle gezilemeyen
|
|
47
|
+
* ve ekran okuyucunun hiç duyurmadığı bir kart üretir.
|
|
48
|
+
*
|
|
49
|
+
* FİZİK YALNIZ TIKLANABİLİR HÂLDE. Kitin 1. yasası (kenar + kaymış gölge,
|
|
50
|
+
* hover'da yükselme, basınca oturma) tıklanan şeyler içindir; okunan bir
|
|
51
|
+
* karonun hover'da oynaması, tıklanabilir olduğu yalanını söyler.
|
|
52
|
+
*
|
|
53
|
+
* Sayı `tabular-nums`: değişen bir sayaç, rakam genişlikleri eşit olmadığı
|
|
54
|
+
* sürece her güncellemede yatay olarak zıplar.
|
|
55
|
+
*
|
|
56
|
+
* `delta` bir yön taşır ama İYİ/KÖTÜ taşımaz — artan bir hata oranı da
|
|
57
|
+
* artıştır. Anlamı `better` veriyor: yükselmesi mi iyi, düşmesi mi.
|
|
58
|
+
*
|
|
59
|
+
* SAYI HER ZAMAN MÜREKKEP RENGİNDE. Bir süre `attention` diye bir bayrak
|
|
60
|
+
* vardı ve sayıyı kritik renge boyuyordu; kaldırıldı. Tek tüketicisinde beş
|
|
61
|
+
* karonun üçü kırmızıydı, ve üç kırmızı hiçbir şeyi öne çıkarmıyor — yalnız
|
|
62
|
+
* paneli alarm hâlinde gösteriyor. Bir sayının kötü olduğunu söylemenin yeri
|
|
63
|
+
* `delta` (yönü ve anlamı olan bir değişim) ya da karonun açtığı ekranın
|
|
64
|
+
* kendisi; karonun rengi değil.
|
|
65
|
+
*
|
|
66
|
+
* `accent` DENENDİ VE KALDIRILDI. Karonun sol kenarında verinin kendi rengini
|
|
67
|
+
* (bir iade adımının rengi) taşıyan ince bir şerit vardı. Tek tüketicisi vardı
|
|
68
|
+
* ve o tüketici reddetti: renk zaten listedeki rozette ve çiplerde duruyordu,
|
|
69
|
+
* kartın kenarında üçüncü kez söylenince aynı bileşen iki ekranda farklı
|
|
70
|
+
* görünüyordu. Tüketicisi olmayan bir prop, `SkeletonKpi`ın yıllarca yaşadığı
|
|
71
|
+
* boşluğun aynısı: duruyor, belgeleniyor, hiçbir şeyin yerini tutmuyor.
|
|
72
|
+
*/
|
|
73
|
+
export function Kpi({ label, value, unit, icon, note, delta, better = "up", chart, href, onClick, pressed, linkComponent: Link = PlainAnchor, className, }) {
|
|
74
|
+
const tone = delta === undefined || delta === 0
|
|
75
|
+
? "neutral"
|
|
76
|
+
: (delta > 0) === (better === "up")
|
|
77
|
+
? "positive"
|
|
78
|
+
: "danger";
|
|
79
|
+
const c = toneOf(tone);
|
|
80
|
+
const govde = (_jsxs(_Fragment, { children: [icon ? (_jsx("span", { className: "tamga-kpi-tile", "aria-hidden": true, children: _jsx(Icon, { icon: icon, size: "base" }) })) : null, _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "tamga-label", children: label }), _jsxs("span", { className: "mt-1 flex items-end gap-2", children: [_jsx("span", { className: "font-mono text-display-sm leading-none font-bold tabular-nums text-ink", children: value }), unit ? (_jsx("span", { className: "font-mono text-small font-medium text-ink-faint", children: unit })) : null, delta !== undefined ? (_jsxs("span", { className: "ml-auto font-mono text-small tabular-nums", style: { color: c.mark }, children: [delta > 0 ? "+" : "", delta, "%"] })) : null] }), note ? _jsx("span", { className: "mt-1 text-caption text-ink-faint", children: note }) : null, chart ? _jsx("span", { className: "mt-4 block", children: chart }) : null] })] }));
|
|
81
|
+
const sinif = cn("tamga-kpi", (href || onClick) && "tamga-kpi-live", className);
|
|
82
|
+
if (href) {
|
|
83
|
+
return (_jsx(Link, { href: href, className: sinif, children: govde }));
|
|
84
|
+
}
|
|
85
|
+
if (onClick) {
|
|
86
|
+
return (_jsx("button", { type: "button", onClick: onClick, "aria-pressed": pressed, "data-pressed": pressed || undefined, className: sinif, children: govde }));
|
|
87
|
+
}
|
|
88
|
+
return _jsx("div", { className: sinif, children: govde });
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* KPI karolarının ızgarası.
|
|
92
|
+
*
|
|
93
|
+
* NEDEN AYRI BİR BİLEŞEN: karoların dizilişi her ekranda elle yazılıyordu
|
|
94
|
+
* (`grid gap-4 sm:grid-cols-2 xl:grid-cols-5`) ve ekrandan ekrana tutmuyordu —
|
|
95
|
+
* biri dörtlü, öteki beşli, bir üçüncüsü boşluğu 3 veriyordu. Izgara karonun
|
|
96
|
+
* kendi işi değil ama karo KÜMESİNİN işi, ve tek yerde durunca hepsi tutuyor.
|
|
97
|
+
*
|
|
98
|
+
* SÜTUN SAYISI VERİLMİYOR, SAYILIYOR: kaç karo varsa o kadar sütun (en çok
|
|
99
|
+
* altı). Elle verilen bir sütun sayısı, karo eklenince yalnız o ekranda
|
|
100
|
+
* güncelleniyor ve tek başına kalan bir karo tam satırı kaplıyor.
|
|
101
|
+
*/
|
|
102
|
+
export function KpiGrid({ children, className }) {
|
|
103
|
+
const sayi = Math.min(Children.count(children), 6);
|
|
104
|
+
return (_jsx("div", { className: cn("tamga-kpi-grid", className), style: { "--tamga-kpi-cols": sayi }, children: children }));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Kod bloğu, kopyalanabilir.
|
|
108
|
+
*
|
|
109
|
+
* Bir panelde bu her zaman aynı üç şeydir: API anahtarı, webhook adresi,
|
|
110
|
+
* kurulum komutu. Üçünde de kullanıcının yaptığı tek şey KOPYALAMAK — o yüzden
|
|
111
|
+
* kopyalama düğmesi bir seçenek değil, bileşenin kendisi.
|
|
112
|
+
*
|
|
113
|
+
* Kopyalama panosu her yerde çalışmaz (HTTPS olmayan bir kaynakta, ya da
|
|
114
|
+
* izin verilmemişse). Başarısızlık SESSİZ olmamalı: düğme metni değişmezse
|
|
115
|
+
* kullanıcı kopyalandığını sanır ve boş yapıştırır.
|
|
116
|
+
*/
|
|
117
|
+
export function Code({ children, labels, className, }) {
|
|
118
|
+
const [state, setState] = useState("idle");
|
|
119
|
+
const timer = useRef(undefined);
|
|
120
|
+
/* Zamanlayıcı bir ref'te tutuluyor ve her tıklamada sıfırlanıyor.
|
|
121
|
+
|
|
122
|
+
Öncesi çıplak bir `setTimeout`'tu ve iki şeyi birden bozuyordu: art arda
|
|
123
|
+
iki tıklamada ilk zamanlayıcı hâlâ çalıştığı için etiket erken geri
|
|
124
|
+
dönüyordu, ve bileşen 1.6 saniye içinde sökülürse zamanlayıcı ölü bir
|
|
125
|
+
bileşene yazmaya çalışıyordu. */
|
|
126
|
+
useEffect(() => () => window.clearTimeout(timer.current), []);
|
|
127
|
+
async function copy() {
|
|
128
|
+
try {
|
|
129
|
+
await navigator.clipboard.writeText(children);
|
|
130
|
+
setState("done");
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
setState("failed");
|
|
134
|
+
}
|
|
135
|
+
window.clearTimeout(timer.current);
|
|
136
|
+
timer.current = window.setTimeout(() => setState("idle"), 1600);
|
|
137
|
+
}
|
|
138
|
+
return (_jsxs("div", { className: cn("tamga-code-block tamga-surface relative overflow-hidden", className), children: [_jsx("pre", { className: "tamga-scroll-x px-4 py-3 font-mono text-caption text-ink", children: children }), _jsxs("button", { type: "button", onClick: copy, className: "tamga-mini-btn absolute top-2 right-2 w-auto gap-1 px-2", "aria-label": labels.copy, children: [_jsx(Icon, { icon: state === "done" ? Check : Customize, size: "xs" }), _jsx("span", { className: "text-caption", children: state === "done" ? labels.copied : state === "failed" ? labels.failed : labels.copy })] })] }));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Klavye tuşu.
|
|
142
|
+
*
|
|
143
|
+
* `<kbd>` KULLANILIYOR, `<span>` değil: bir ekran okuyucu için "Ctrl" ile
|
|
144
|
+
* "bir tuşa basılacak" arasındaki fark bu elemanda yaşıyor.
|
|
145
|
+
*/
|
|
146
|
+
export function Kbd({ children, className }) {
|
|
147
|
+
return (_jsx("kbd", { className: cn("tamga-surface inline-flex h-5 min-w-5 items-center justify-center px-1.5 font-mono text-caption text-ink-soft", className), children: children }));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Sayaç rozeti — bir ikonun köşesindeki rakam.
|
|
151
|
+
*
|
|
152
|
+
* `StatusChip` bir DURUM taşır ("Yayında"), bu bir SAYI taşır ("3"). İkisi
|
|
153
|
+
* ayrı bileşen çünkü ayrı şeyler: durum okunur, sayı sayılır.
|
|
154
|
+
*
|
|
155
|
+
* `max` üstünde "+" ile kesiliyor. Kesilmezse dört haneli bir sayı rozetin
|
|
156
|
+
* kutusunu şişirir ve altındaki ikonu ezer.
|
|
157
|
+
*
|
|
158
|
+
* Sıfır GÖSTERİLMEZ: "0 bildirim" bir bilgi değil, gürültüdür.
|
|
159
|
+
*/
|
|
160
|
+
export function Badge({ count, max = 99, tone = "danger", label, className, children, }) {
|
|
161
|
+
if (count <= 0)
|
|
162
|
+
return _jsx(_Fragment, { children: children });
|
|
163
|
+
const c = toneOf(tone);
|
|
164
|
+
const shown = count > max ? `${max}+` : String(count);
|
|
165
|
+
const dot = (_jsxs("span", { className: "inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 font-mono text-micro leading-none font-semibold tabular-nums", style: { background: c.mark, color: "var(--color-page)" }, children: [_jsx("span", { "aria-hidden": true, children: shown }), _jsxs("span", { className: "sr-only", children: [count, " ", label] })] }));
|
|
166
|
+
if (!children)
|
|
167
|
+
return dot;
|
|
168
|
+
return (_jsxs("span", { className: cn("relative inline-flex", className), children: [children, _jsx("span", { className: "absolute -top-1 -right-1", children: dot })] }));
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Adım göstergesi.
|
|
172
|
+
*
|
|
173
|
+
* Üründe bir sihirbaz şablonu vardı ve adım göstergesi yoktu — yani kullanıcı
|
|
174
|
+
* kaçıncı adımda olduğunu ve kaç adım kaldığını bilmiyordu. Bir sihirbazın
|
|
175
|
+
* varlık sebebi tam olarak budur.
|
|
176
|
+
*
|
|
177
|
+
* `<ol>` kullanılıyor: adımlar SIRALI ve sıra bilgi taşıyor. Ekran okuyucu
|
|
178
|
+
* "3 öğeli liste, öğe 2" der; `<div>`'lerle bu bilgi kaybolur.
|
|
179
|
+
*
|
|
180
|
+
* Tamamlanan adım bir onay işareti alıyor, aktif olan aksan kenarı, gelecek
|
|
181
|
+
* olan sönük. Üç durum üç ayrı görsel dil — çünkü "neredeyim" ve "ne kaldı"
|
|
182
|
+
* ayrı iki soru.
|
|
183
|
+
*/
|
|
184
|
+
export function Steps({ steps, current, className, }) {
|
|
185
|
+
return (_jsx("ol", { className: cn("flex flex-wrap items-center gap-x-3 gap-y-2", className), children: steps.map((s, i) => {
|
|
186
|
+
const done = i < current;
|
|
187
|
+
const active = i === current;
|
|
188
|
+
return (_jsxs("li", { className: "flex items-center gap-3", children: [_jsx("span", { className: "tamga-step-mark font-mono text-caption", "data-state": done ? "done" : active ? "current" : "todo", "aria-hidden": true, children: done ? _jsx(Icon, { icon: Check, size: "xs", weight: "bold" }) : i + 1 }), _jsx("span", { className: cn("text-small", active ? "font-medium text-ink" : "text-ink-faint"), "aria-current": active ? "step" : undefined, children: s }), i < steps.length - 1 ? (_jsx("span", { "aria-hidden": true, className: "tamga-step-line", "data-done": done ? "true" : "false" })) : null] }, s));
|
|
189
|
+
}) }));
|
|
190
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* BOŞ DURUM SİSTEMİ — dört slot, ve hiçbiri neyin eksik olduğunu bilmez.
|
|
4
|
+
*
|
|
5
|
+
* Gerekçe: docs/gerekce/04-bos-ve-hata.md
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Çizimi çizen fonksiyon. Slot boyutu ve süzülme durumunu verir; ne çizildiği
|
|
9
|
+
* çağıranın işi.
|
|
10
|
+
*/
|
|
11
|
+
export type Art = (opts: {
|
|
12
|
+
size: number;
|
|
13
|
+
float: boolean;
|
|
14
|
+
}) => ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* The speech bubble — the answer to "and the figure says …".
|
|
17
|
+
*
|
|
18
|
+
* It is the one part of the mascot that IS in the system: 1px edge plus a 2px
|
|
19
|
+
* hard offset, exactly like every other raised object. That pairing is the
|
|
20
|
+
* whole trick — it lets pixel art sit inside a strict interface without either
|
|
21
|
+
* one looking lost.
|
|
22
|
+
*
|
|
23
|
+
* The line is real text, not decoration, so it is readable. Keep it to one
|
|
24
|
+
* short sentence: the bubble is a voice, not a paragraph.
|
|
25
|
+
*/
|
|
26
|
+
export declare function SaysBubble({ children, art, size, float, className, }: {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
art: Art;
|
|
29
|
+
size?: number;
|
|
30
|
+
float?: boolean;
|
|
31
|
+
className?: string;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
/**
|
|
34
|
+
* A row that has nothing in it.
|
|
35
|
+
*
|
|
36
|
+
* The action goes hard right, on the row's own baseline — that is where every
|
|
37
|
+
* other row in the product keeps its control, and an empty row that puts its
|
|
38
|
+
* button somewhere else stops looking like part of the list.
|
|
39
|
+
*
|
|
40
|
+
* No border of its own: it drops into a table body or a list that already has
|
|
41
|
+
* one, and it inherits that list's row rhythm.
|
|
42
|
+
*/
|
|
43
|
+
export declare function EmptyNote({ art, title, children, action, className, }: {
|
|
44
|
+
art: Art;
|
|
45
|
+
title: string;
|
|
46
|
+
children?: React.ReactNode;
|
|
47
|
+
action?: React.ReactNode;
|
|
48
|
+
className?: string;
|
|
49
|
+
}): import("react").JSX.Element;
|
|
50
|
+
export type EmptyLayout = "banner" | "ticket" | "routes";
|
|
51
|
+
export type EmptyRoute = {
|
|
52
|
+
label: string;
|
|
53
|
+
note?: string;
|
|
54
|
+
onClick?: () => void;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A surface that has nothing in it.
|
|
58
|
+
*
|
|
59
|
+
* Three layouts, one set of content. They are NOT three styles of the same
|
|
60
|
+
* picture — each answers a different question about the screen underneath:
|
|
61
|
+
*
|
|
62
|
+
* banner wide and horizontal: art left, words centre, action at the far
|
|
63
|
+
* right edge, numbered base plate underneath. Reads as a strip
|
|
64
|
+
* across the top of a working screen.
|
|
65
|
+
* ticket a narrow tag pinned to a big surface, with a perforation and one
|
|
66
|
+
* full-width action. For a single unambiguous next step.
|
|
67
|
+
* routes the words and the figure on the left, a list of ways in on the right.
|
|
68
|
+
* Two buttons ask a yes/no question; a list answers "what can I even
|
|
69
|
+
* do here", which is what somebody arriving actually wants to know.
|
|
70
|
+
*
|
|
71
|
+
* Each one also wants a different drawing. The layout decides the shape; the
|
|
72
|
+
* drawing decides what the emptiness FEELS like, and those are two choices,
|
|
73
|
+
* not one.
|
|
74
|
+
*/
|
|
75
|
+
export declare function EmptyState({ layout, art, kicker, title, children, action, note, says, steps, routes, size, className, }: {
|
|
76
|
+
layout?: EmptyLayout;
|
|
77
|
+
art: Art;
|
|
78
|
+
/** what KIND of nothing this is: "no records", "no results" TR: bunun NE TÜR bir hiçlik olduğu: "kayıt yok", "sonuç yok" */
|
|
79
|
+
kicker?: string;
|
|
80
|
+
title: string;
|
|
81
|
+
/** why it is empty, in the instrument's voice TR: neden boş olduğu, göstergenin diliyle */
|
|
82
|
+
children?: React.ReactNode;
|
|
83
|
+
action?: React.ReactNode;
|
|
84
|
+
/** the quiet second line: what happens next TR: sessiz ikinci satır: bundan sonra ne olacağı */
|
|
85
|
+
note?: string;
|
|
86
|
+
/**
|
|
87
|
+
* one short line in a bubble over the figure's head TR: figürün başının üstündeki balonda tek
|
|
88
|
+
* kısa satır
|
|
89
|
+
*/
|
|
90
|
+
says?: React.ReactNode;
|
|
91
|
+
/**
|
|
92
|
+
* banner only: what happens after the click, on a seated base plate TR: yalnız afişte:
|
|
93
|
+
* tıklamadan sonra ne olacağı, oturmuş bir taban plakasında
|
|
94
|
+
*/
|
|
95
|
+
steps?: string[];
|
|
96
|
+
/** routes only: the ways in TR: yalnız rotalar: içeri giden yollar */
|
|
97
|
+
routes?: EmptyRoute[];
|
|
98
|
+
size?: number;
|
|
99
|
+
className?: string;
|
|
100
|
+
}): import("react").JSX.Element;
|
|
101
|
+
/**
|
|
102
|
+
* A whole body with nothing in it — and no box drawn around the nothing.
|
|
103
|
+
*
|
|
104
|
+
* This is the one that goes straight onto the page, not inside a card. A
|
|
105
|
+
* bordered panel needs content to bound; when the entire screen is empty there
|
|
106
|
+
* is nothing to bound, and the border becomes a frame around a void. So there
|
|
107
|
+
* is no border, no ground, no offset. The only structure is a hard floor rule
|
|
108
|
+
* as wide as the figure — enough to say he is standing somewhere, not enough to be a
|
|
109
|
+
* box.
|
|
110
|
+
*
|
|
111
|
+
* Reach for it when somebody lands on a section they have never used. For an
|
|
112
|
+
* empty area INSIDE a working screen, use EmptyState instead: that one has a
|
|
113
|
+
* card around it because the rest of the screen does too.
|
|
114
|
+
*/
|
|
115
|
+
export declare function EmptyBlank({ art, kicker, title, children, action, says, size, className, }: {
|
|
116
|
+
art: Art;
|
|
117
|
+
kicker?: string;
|
|
118
|
+
title: string;
|
|
119
|
+
children?: React.ReactNode;
|
|
120
|
+
action?: React.ReactNode;
|
|
121
|
+
says?: React.ReactNode;
|
|
122
|
+
size?: number;
|
|
123
|
+
className?: string;
|
|
124
|
+
}): import("react").JSX.Element;
|
|
125
|
+
/**
|
|
126
|
+
* A choice you are offering — a template, a starting point, a preset.
|
|
127
|
+
*
|
|
128
|
+
* It is a real button, so it obeys raised physics: 1px edge, 2px offset, lifts
|
|
129
|
+
* under the cursor, presses flat when clicked. A template you cannot pick has
|
|
130
|
+
* no business looking like a card.
|
|
131
|
+
*
|
|
132
|
+
* The category colour is a 3px SPINE under the art rather than a coloured
|
|
133
|
+
* banner behind it. A wall of coloured banners would out-shout a real event
|
|
134
|
+
* (Yasa 3); a rule carries the same grouping and outranks nothing.
|
|
135
|
+
*/
|
|
136
|
+
export declare function EmptyTile({ art, kicker, title, children, tone, onClick, className, }: {
|
|
137
|
+
art: Art;
|
|
138
|
+
/** the identifier-shaped line above the title TR: başlığın üstündeki tanımlayıcı biçimli satır */
|
|
139
|
+
kicker?: string;
|
|
140
|
+
title: string;
|
|
141
|
+
children?: React.ReactNode;
|
|
142
|
+
/**
|
|
143
|
+
* 1–5, the categorical series tones, deliberately not status colours TR: 1–5, kategorik seri
|
|
144
|
+
* tonları, bilerek durum renkleri değil
|
|
145
|
+
*/
|
|
146
|
+
tone?: 1 | 2 | 3 | 4 | 5;
|
|
147
|
+
onClick?: () => void;
|
|
148
|
+
className?: string;
|
|
149
|
+
}): import("react").JSX.Element;
|
|
150
|
+
//# sourceMappingURL=empty-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../src/components/empty-state.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from "./icon.js";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { CaretRight } from "./icons.js";
|
|
5
|
+
/**
|
|
6
|
+
* The drawing standing on the ground.
|
|
7
|
+
*
|
|
8
|
+
* `ground` adds the hard cast shadow — a BAR, deliberately, not a copy of his
|
|
9
|
+
* silhouette: a body-shaped offset is the pressable-object signature, and the figure
|
|
10
|
+
* is not pressable. A bar reads as ground, not as affordance. It lands down and
|
|
11
|
+
* to the right because that is where every other shadow in the kit lands.
|
|
12
|
+
*/
|
|
13
|
+
function ArtFigure({ art, size, float, ground = true, }) {
|
|
14
|
+
return (_jsx("span", { className: "tamga-art-fig", "data-ground": ground, children: art({ size, float: Boolean(float) }) }));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The speech bubble — the answer to "and the figure says …".
|
|
18
|
+
*
|
|
19
|
+
* It is the one part of the mascot that IS in the system: 1px edge plus a 2px
|
|
20
|
+
* hard offset, exactly like every other raised object. That pairing is the
|
|
21
|
+
* whole trick — it lets pixel art sit inside a strict interface without either
|
|
22
|
+
* one looking lost.
|
|
23
|
+
*
|
|
24
|
+
* The line is real text, not decoration, so it is readable. Keep it to one
|
|
25
|
+
* short sentence: the bubble is a voice, not a paragraph.
|
|
26
|
+
*/
|
|
27
|
+
export function SaysBubble({ children, art, size = 168, float = true, className, }) {
|
|
28
|
+
return (_jsxs("div", { className: cn("relative shrink-0 pt-11", float && "tamga-float", className), children: [_jsxs("div", { className: "absolute top-0 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap", children: [_jsx("div", { className: "px-3 py-1.5 text-small font-medium text-ink", style: {
|
|
29
|
+
background: "var(--color-shell)",
|
|
30
|
+
border: "1px solid var(--color-edge)",
|
|
31
|
+
borderRadius: "var(--radius-card)",
|
|
32
|
+
boxShadow: "2px 2px 0 var(--color-edge)",
|
|
33
|
+
}, children: children }), _jsx("span", { "aria-hidden": true, className: "absolute top-full left-1/2 block h-2 w-2 -translate-x-1/2 -translate-y-px rotate-45", style: {
|
|
34
|
+
background: "var(--color-shell)",
|
|
35
|
+
borderRight: "1px solid var(--color-edge)",
|
|
36
|
+
borderBottom: "1px solid var(--color-edge)",
|
|
37
|
+
} })] }), _jsx(ArtFigure, { art: art, size: size })] }));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A row that has nothing in it.
|
|
41
|
+
*
|
|
42
|
+
* The action goes hard right, on the row's own baseline — that is where every
|
|
43
|
+
* other row in the product keeps its control, and an empty row that puts its
|
|
44
|
+
* button somewhere else stops looking like part of the list.
|
|
45
|
+
*
|
|
46
|
+
* No border of its own: it drops into a table body or a list that already has
|
|
47
|
+
* one, and it inherits that list's row rhythm.
|
|
48
|
+
*/
|
|
49
|
+
export function EmptyNote({ art, title, children, action, className, }) {
|
|
50
|
+
return (_jsxs("div", { className: cn("tamga-gutter flex items-center gap-4 py-6", className), children: [_jsx("span", { className: "tamga-art-port tamga-art-well", children: _jsx(ArtFigure, { art: art, size: 56, ground: false }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-body font-medium text-ink", children: title }), children ? (_jsx("p", { className: "mt-1 max-w-[var(--measure)] text-small leading-relaxed text-ink-faint", children: children })) : null] }), action ? _jsx("div", { className: "shrink-0", children: action }) : null] }));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A surface that has nothing in it.
|
|
54
|
+
*
|
|
55
|
+
* Three layouts, one set of content. They are NOT three styles of the same
|
|
56
|
+
* picture — each answers a different question about the screen underneath:
|
|
57
|
+
*
|
|
58
|
+
* banner wide and horizontal: art left, words centre, action at the far
|
|
59
|
+
* right edge, numbered base plate underneath. Reads as a strip
|
|
60
|
+
* across the top of a working screen.
|
|
61
|
+
* ticket a narrow tag pinned to a big surface, with a perforation and one
|
|
62
|
+
* full-width action. For a single unambiguous next step.
|
|
63
|
+
* routes the words and the figure on the left, a list of ways in on the right.
|
|
64
|
+
* Two buttons ask a yes/no question; a list answers "what can I even
|
|
65
|
+
* do here", which is what somebody arriving actually wants to know.
|
|
66
|
+
*
|
|
67
|
+
* Each one also wants a different drawing. The layout decides the shape; the
|
|
68
|
+
* drawing decides what the emptiness FEELS like, and those are two choices,
|
|
69
|
+
* not one.
|
|
70
|
+
*/
|
|
71
|
+
export function EmptyState({ layout = "banner", art, kicker, title, children, action, note, says, steps, routes, size = 150, className, }) {
|
|
72
|
+
const figure = says !== undefined ? (_jsx(SaysBubble, { art: art, size: size, children: says })) : (_jsx(ArtFigure, { art: art, size: size, float: true }));
|
|
73
|
+
const words = (_jsxs(_Fragment, { children: [kicker ? _jsx("p", { className: "tamga-kicker mb-2", children: kicker }) : null, _jsx("h3", { className: "text-display-sm font-semibold tracking-tight text-ink", children: title }), children ? (_jsx("p", { className: "mt-3 max-w-[var(--measure)] text-control leading-relaxed text-ink-soft", children: children })) : null, note ? (_jsx("p", { className: "mt-2 max-w-[var(--measure)] text-small leading-relaxed text-ink-faint", children: note })) : null] }));
|
|
74
|
+
if (layout === "banner") {
|
|
75
|
+
return (_jsxs("div", { className: cn("flex flex-col overflow-hidden", className), children: [_jsxs("div", { className: "flex flex-1 flex-col sm:flex-row sm:items-stretch", children: [_jsx("div", { className: "tamga-art-well tamga-art-stage tamga-art-floor sm:border-b-0", children: figure }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-7 px-6 py-7 sm:flex-row sm:items-center sm:justify-between sm:gap-10 sm:px-9 sm:py-9", children: [_jsx("div", { className: "min-w-0", children: words }), action ? (_jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-3", children: action })) : null] })] }), steps?.length ? (_jsx("ol", { className: "tamga-art-steps tamga-art-well", children: steps.map((step, i) => (_jsxs("li", { className: "tamga-art-step", children: [_jsx("span", { className: "tamga-art-step-n", children: String(i + 1).padStart(2, "0") }), _jsx("span", { className: "text-small leading-relaxed text-ink-soft", children: step })] }, step))) })) : null] }));
|
|
76
|
+
}
|
|
77
|
+
if (layout === "ticket") {
|
|
78
|
+
return (_jsx("div", { className: cn("flex justify-center px-6 py-10", className), children: _jsxs("div", { className: "tamga-card w-full max-w-90 overflow-hidden", children: [_jsx("div", { className: "tamga-art-well tamga-art-band", children: figure }), _jsx("div", { className: "px-6 pt-6 pb-7 text-center [&_p]:mx-auto", children: words }), _jsx("div", { className: "tamga-art-divider" }), action ? (_jsx("div", { className: "flex flex-col gap-2 p-4 [&>*]:w-full [&>*]:justify-center", children: action })) : null] }) }));
|
|
79
|
+
}
|
|
80
|
+
/* routes — three columns on one line: the figure, the words, the ways in */
|
|
81
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-8 px-6 py-9 sm:flex-row sm:items-center sm:gap-10 sm:px-10 sm:py-10", className), children: [_jsx("div", { className: "flex shrink-0 items-end", children: says !== undefined ? (_jsx(SaysBubble, { art: art, size: size, children: says })) : (_jsx(ArtFigure, { art: art, size: size, float: true })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [words, action ? _jsx("div", { className: "mt-6 flex flex-wrap gap-3", children: action }) : null] }), _jsxs("div", { className: "flex w-full shrink-0 flex-col gap-2.5 sm:max-w-76", children: [_jsx("p", { className: "tamga-kicker mb-0.5", children: "ways in" }), routes?.map((r) => (_jsxs("button", { type: "button", onClick: r.onClick, className: "tamga-art-route", children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block text-body font-medium text-ink", children: r.label }), r.note ? _jsx("span", { className: "block text-small text-ink-faint", children: r.note }) : null] }), _jsx(Icon, { icon: CaretRight, size: "sm" })] }, r.label)))] })] }));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A whole body with nothing in it — and no box drawn around the nothing.
|
|
85
|
+
*
|
|
86
|
+
* This is the one that goes straight onto the page, not inside a card. A
|
|
87
|
+
* bordered panel needs content to bound; when the entire screen is empty there
|
|
88
|
+
* is nothing to bound, and the border becomes a frame around a void. So there
|
|
89
|
+
* is no border, no ground, no offset. The only structure is a hard floor rule
|
|
90
|
+
* as wide as the figure — enough to say he is standing somewhere, not enough to be a
|
|
91
|
+
* box.
|
|
92
|
+
*
|
|
93
|
+
* Reach for it when somebody lands on a section they have never used. For an
|
|
94
|
+
* empty area INSIDE a working screen, use EmptyState instead: that one has a
|
|
95
|
+
* card around it because the rest of the screen does too.
|
|
96
|
+
*/
|
|
97
|
+
export function EmptyBlank({ art, kicker, title, children, action, says, size = 230, className, }) {
|
|
98
|
+
return (_jsxs("div", { className: cn("flex w-full flex-col items-center justify-center px-6 py-16 text-center", className), children: [says !== undefined ? (_jsx(SaysBubble, { art: art, size: size, children: says })) : (_jsx(ArtFigure, { art: art, size: size, float: true, ground: false })), _jsx("span", { className: "tamga-art-blank-floor mt-1 mb-8", style: { width: size * 0.72 } }), kicker ? _jsx("p", { className: "tamga-kicker mb-2", children: kicker }) : null, _jsx("h3", { className: "text-display-sm font-semibold tracking-tight text-ink", children: title }), children ? (_jsx("p", { className: "mt-3 max-w-[var(--measure)] text-control leading-relaxed text-ink-soft", children: children })) : null, action ? (_jsx("div", { className: "mt-8 flex flex-wrap items-center justify-center gap-3", children: action })) : null] }));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A choice you are offering — a template, a starting point, a preset.
|
|
102
|
+
*
|
|
103
|
+
* It is a real button, so it obeys raised physics: 1px edge, 2px offset, lifts
|
|
104
|
+
* under the cursor, presses flat when clicked. A template you cannot pick has
|
|
105
|
+
* no business looking like a card.
|
|
106
|
+
*
|
|
107
|
+
* The category colour is a 3px SPINE under the art rather than a coloured
|
|
108
|
+
* banner behind it. A wall of coloured banners would out-shout a real event
|
|
109
|
+
* (Yasa 3); a rule carries the same grouping and outranks nothing.
|
|
110
|
+
*/
|
|
111
|
+
export function EmptyTile({ art, kicker, title, children, tone = 1, onClick, className, }) {
|
|
112
|
+
return (_jsxs("button", { type: "button", onClick: onClick, className: cn("tamga-art-tile", className), children: [_jsx("span", { className: "tamga-art-well flex h-28 items-end justify-center pt-4", children: _jsx(ArtFigure, { art: art, size: 86 }) }), _jsx("span", { className: "tamga-art-spine", "data-tone": tone }), _jsxs("span", { className: "tamga-gutter flex min-w-0 flex-1 flex-col py-4", children: [kicker ? _jsx("span", { className: "tamga-kicker mb-1.5", children: kicker }) : null, _jsx("span", { className: "text-body font-semibold text-ink", children: title }), children ? (_jsx("span", { className: "mt-1 text-small leading-relaxed text-ink-faint", children: children })) : null] })] }));
|
|
113
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The third state, and the one the kit was missing.
|
|
3
|
+
*
|
|
4
|
+
* Gerekçe: docs/gerekce/04-bos-ve-hata.md
|
|
5
|
+
*/
|
|
6
|
+
export declare function ErrorState({ title, description, detail, onRetry, retryLabel, compact, }: {
|
|
7
|
+
title?: string;
|
|
8
|
+
/**
|
|
9
|
+
* plain words: what the person cannot do right now TR: düz sözcüklerle: kişinin şu an ne
|
|
10
|
+
* yapamadığı
|
|
11
|
+
*/
|
|
12
|
+
description?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The technical line. SETTLED 2026-08-19 (Ercüment, open question 5) by the locked envelope
|
|
15
|
+
* (api-kontrati.html A4): print `code` and `meta.request_id`, in that order, e.g.
|
|
16
|
+
* `quota_exceeded · req_8f2a…`. Why those two and not the HTTP status: `code` is the stable
|
|
17
|
+
* machine name for what went wrong (it never localises, and it is what support and the
|
|
18
|
+
* programmatic client both branch on), and `request_id` is what matches this failure to a
|
|
19
|
+
* server log; the contract makes it mandatory on 500 and mirrors it in `X-Request-Id`. The
|
|
20
|
+
* human sentence belongs in `description`, from the envelope's already-localised `message`.
|
|
21
|
+
* TR: Teknik satır. 2026-08-19'da KARARA BAĞLANDI (Ercüment, açık soru 5): kilitli zarf
|
|
22
|
+
* (api-kontrati.html A4) uyarınca `code` ve `meta.request_id`, bu sırayla, örneğin
|
|
23
|
+
* `quota_exceeded · req_8f2a…`. Neden bu ikisi ve HTTP durumu değil: `code` neyin
|
|
24
|
+
* bozulduğunun kararlı makine adı (hiç yerelleşmiyor, ve hem destek hem programatik istemci
|
|
25
|
+
* ona bakarak dallanıyor), `request_id` ise bu hatayı bir sunucu kaydıyla eşleştiren şey;
|
|
26
|
+
* kontrat onu 500'de zorunlu kılıyor ve `X-Request-Id` başlığında aynalıyor. İnsan cümlesi
|
|
27
|
+
* zarfın zaten yerelleşmiş `message` alanından `description`'a ait.
|
|
28
|
+
*/
|
|
29
|
+
detail?: string;
|
|
30
|
+
onRetry?: () => void;
|
|
31
|
+
retryLabel?: string;
|
|
32
|
+
/**
|
|
33
|
+
* inside a card or a panel rather than a whole page TR: bütün bir sayfa yerine bir kartın ya
|
|
34
|
+
* da panelin içinde
|
|
35
|
+
*/
|
|
36
|
+
compact?: boolean;
|
|
37
|
+
}): import("react").JSX.Element;
|
|
38
|
+
//# sourceMappingURL=error-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-state.d.ts","sourceRoot":"","sources":["../../src/components/error-state.tsx"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAA6B,EAC7B,WAAW,EACX,MAAM,EACN,OAAO,EACP,UAAwB,EACxB,OAAe,GAChB,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,+BAuCA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from "./icon.js";
|
|
3
|
+
import { Refresh, Warning } from "./icons.js";
|
|
4
|
+
import { Button } from "./button.js";
|
|
5
|
+
/**
|
|
6
|
+
* The third state, and the one the kit was missing.
|
|
7
|
+
*
|
|
8
|
+
* Gerekçe: docs/gerekce/04-bos-ve-hata.md
|
|
9
|
+
*/
|
|
10
|
+
export function ErrorState({ title = "Could not load this", description, detail, onRetry, retryLabel = "Try again", compact = false, }) {
|
|
11
|
+
return (_jsxs("div", { role: "alert", "aria-live": "polite", className: `tamga-gutter flex flex-col items-start gap-4 ${compact ? "py-6" : "py-12"}`, children: [_jsx("span", { className: "flex size-10 items-center justify-center border", style: {
|
|
12
|
+
borderRadius: "var(--radius-ctl)",
|
|
13
|
+
borderColor: "var(--color-critical)",
|
|
14
|
+
color: "var(--color-critical)",
|
|
15
|
+
}, children: _jsx(Icon, { icon: Warning, size: "base" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-body font-medium text-ink", children: title }), description ? (_jsx("p", { className: "mt-1 max-w-[var(--measure)] text-small leading-relaxed text-ink-faint", children: description })) : null, detail ? (_jsx("p", { className: "mt-2 max-w-[var(--measure)] break-words font-mono text-caption text-ink-faint", children: detail })) : null] }), onRetry ? (_jsxs(Button, { onClick: onRetry, children: [_jsx(Icon, { icon: Refresh, size: "sm" }), " ", retryLabel] })) : null] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileUpload — ürün görselleri.
|
|
3
|
+
*
|
|
4
|
+
* Gerekçe: docs/gerekce/01-form-ve-girdi.md
|
|
5
|
+
*/
|
|
6
|
+
export type UploadItem = {
|
|
7
|
+
id: string;
|
|
8
|
+
/**
|
|
9
|
+
* The preview address: `URL.createObjectURL(file)` or a URL from the server. TR: Önizleme
|
|
10
|
+
* adresi: `URL.createObjectURL(file)` ya da sunucudan gelen URL.
|
|
11
|
+
*/
|
|
12
|
+
url: string;
|
|
13
|
+
name: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function FileUpload({ items, onAdd, onRemove, onReorder, accept, multiple, disabled, labels, className, }: {
|
|
16
|
+
items: readonly UploadItem[];
|
|
17
|
+
onAdd?: (files: File[]) => void;
|
|
18
|
+
onRemove?: (id: string) => void;
|
|
19
|
+
onReorder?: (id: string, direction: -1 | 1) => void;
|
|
20
|
+
accept?: string;
|
|
21
|
+
multiple?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
labels: {
|
|
24
|
+
drop: string;
|
|
25
|
+
browse: string;
|
|
26
|
+
remove: string;
|
|
27
|
+
moveLeft: string;
|
|
28
|
+
moveRight: string;
|
|
29
|
+
/** The badge the first image carries: "kapak", "cover" TR: İlk görselin taşıdığı rozet: "kapak", "cover" */
|
|
30
|
+
primary: string;
|
|
31
|
+
};
|
|
32
|
+
className?: string;
|
|
33
|
+
}): import("react").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=file-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.d.ts","sourceRoot":"","sources":["../../src/components/file-upload.tsx"],"names":[],"mappings":"AAOA;;;;GAIG;AAEH,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAAkB,EAClB,QAAe,EACf,QAAgB,EAChB,MAAM,EACN,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,4GAA4G;QAC5G,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA0GA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef, useState } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Icon } from "./icon.js";
|
|
6
|
+
import { Upload, Close, CaretLeft, CaretRight } from "./icons.js";
|
|
7
|
+
export function FileUpload({ items, onAdd, onRemove, onReorder, accept = "image/*", multiple = true, disabled = false, labels, className, }) {
|
|
8
|
+
const id = useId();
|
|
9
|
+
const input = useRef(null);
|
|
10
|
+
const [over, setOver] = useState(false);
|
|
11
|
+
function take(list) {
|
|
12
|
+
if (!list || disabled)
|
|
13
|
+
return;
|
|
14
|
+
onAdd?.(Array.from(list));
|
|
15
|
+
}
|
|
16
|
+
function onDrop(e) {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
setOver(false);
|
|
19
|
+
take(e.dataTransfer.files);
|
|
20
|
+
}
|
|
21
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-4", className), children: [_jsxs("div", { onDragOver: (e) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
if (!disabled)
|
|
24
|
+
setOver(true);
|
|
25
|
+
}, onDragLeave: () => setOver(false), onDrop: onDrop, "data-over": over, className: "tamga-art-well flex flex-col items-center gap-3 px-6 py-10 text-center", style: {
|
|
26
|
+
borderStyle: "dashed",
|
|
27
|
+
borderColor: over ? "var(--color-accent-line)" : undefined,
|
|
28
|
+
}, children: [_jsx(Icon, { icon: Upload, size: "md", className: "text-ink-faint" }), _jsx("p", { className: "text-body text-ink-faint", children: labels.drop }), _jsx("button", { type: "button", className: "tamga-btn tamga-btn-sm", disabled: disabled, onClick: () => input.current?.click(), children: labels.browse }), _jsx("input", { ref: input, id: id, type: "file", accept: accept, multiple: multiple, disabled: disabled, className: "sr-only", onChange: (e) => {
|
|
29
|
+
take(e.target.files);
|
|
30
|
+
/* Aynı dosyayı ikinci kez seçebilmek için: aksi hâlde `change`
|
|
31
|
+
tetiklenmez ve kullanıcı "çalışmıyor" der. */
|
|
32
|
+
e.target.value = "";
|
|
33
|
+
} })] }), items.length > 0 ? (_jsx("ul", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: items.map((it, i) => (_jsxs("li", { className: "tamga-card relative overflow-hidden", children: [_jsx("img", { src: it.url, alt: it.name, className: "aspect-square w-full object-cover" }), i === 0 ? (_jsx("span", { className: "tamga-chip absolute top-1.5 left-1.5", children: labels.primary })) : null, _jsxs("span", { className: "tamga-gutter flex items-center gap-1 py-1.5", children: [_jsx("button", { type: "button", className: "tamga-mini-btn", "aria-label": labels.moveLeft, disabled: i === 0, onClick: () => onReorder?.(it.id, -1), children: _jsx(Icon, { icon: CaretLeft, size: "xs" }) }), _jsx("button", { type: "button", className: "tamga-mini-btn", "aria-label": labels.moveRight, disabled: i === items.length - 1, onClick: () => onReorder?.(it.id, 1), children: _jsx(Icon, { icon: CaretRight, size: "xs" }) }), _jsx("button", { type: "button", className: "tamga-mini-btn ml-auto", "aria-label": labels.remove, onClick: () => onRemove?.(it.id), children: _jsx(Icon, { icon: Close, size: "xs" }) })] })] }, it.id))) })) : null] }));
|
|
34
|
+
}
|