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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/dist/blocks/count-row.d.ts +29 -0
  4. package/dist/blocks/count-row.d.ts.map +1 -0
  5. package/dist/blocks/count-row.js +19 -0
  6. package/dist/blocks/filter-bar.d.ts +123 -0
  7. package/dist/blocks/filter-bar.d.ts.map +1 -0
  8. package/dist/blocks/filter-bar.js +74 -0
  9. package/dist/blocks/index.d.ts +24 -0
  10. package/dist/blocks/index.d.ts.map +1 -0
  11. package/dist/blocks/index.js +23 -0
  12. package/dist/blocks/save-bar.d.ts +31 -0
  13. package/dist/blocks/save-bar.d.ts.map +1 -0
  14. package/dist/blocks/save-bar.js +21 -0
  15. package/dist/components/a11y.d.ts +21 -0
  16. package/dist/components/a11y.d.ts.map +1 -0
  17. package/dist/components/a11y.js +159 -0
  18. package/dist/components/advanced-input.d.ts +115 -0
  19. package/dist/components/advanced-input.d.ts.map +1 -0
  20. package/dist/components/advanced-input.js +189 -0
  21. package/dist/components/avatar.d.ts +63 -0
  22. package/dist/components/avatar.d.ts.map +1 -0
  23. package/dist/components/avatar.js +54 -0
  24. package/dist/components/badge.d.ts +37 -0
  25. package/dist/components/badge.d.ts.map +1 -0
  26. package/dist/components/badge.js +29 -0
  27. package/dist/components/bar.d.ts +32 -0
  28. package/dist/components/bar.d.ts.map +1 -0
  29. package/dist/components/bar.js +33 -0
  30. package/dist/components/button.d.ts +45 -0
  31. package/dist/components/button.d.ts.map +1 -0
  32. package/dist/components/button.js +56 -0
  33. package/dist/components/chart.d.ts +43 -0
  34. package/dist/components/chart.d.ts.map +1 -0
  35. package/dist/components/chart.js +41 -0
  36. package/dist/components/checkbox.d.ts +13 -0
  37. package/dist/components/checkbox.d.ts.map +1 -0
  38. package/dist/components/checkbox.js +16 -0
  39. package/dist/components/chrome.d.ts +87 -0
  40. package/dist/components/chrome.d.ts.map +1 -0
  41. package/dist/components/chrome.js +107 -0
  42. package/dist/components/combobox.d.ts +31 -0
  43. package/dist/components/combobox.d.ts.map +1 -0
  44. package/dist/components/combobox.js +62 -0
  45. package/dist/components/control-base.d.ts +7 -0
  46. package/dist/components/control-base.d.ts.map +1 -0
  47. package/dist/components/control-base.js +6 -0
  48. package/dist/components/data-table.d.ts +68 -0
  49. package/dist/components/data-table.d.ts.map +1 -0
  50. package/dist/components/data-table.js +50 -0
  51. package/dist/components/date-picker.d.ts +52 -0
  52. package/dist/components/date-picker.d.ts.map +1 -0
  53. package/dist/components/date-picker.js +73 -0
  54. package/dist/components/disclosure.d.ts +31 -0
  55. package/dist/components/disclosure.d.ts.map +1 -0
  56. package/dist/components/disclosure.js +32 -0
  57. package/dist/components/display.d.ts +188 -0
  58. package/dist/components/display.d.ts.map +1 -0
  59. package/dist/components/display.js +190 -0
  60. package/dist/components/empty-state.d.ts +150 -0
  61. package/dist/components/empty-state.d.ts.map +1 -0
  62. package/dist/components/empty-state.js +113 -0
  63. package/dist/components/error-state.d.ts +38 -0
  64. package/dist/components/error-state.d.ts.map +1 -0
  65. package/dist/components/error-state.js +16 -0
  66. package/dist/components/file-upload.d.ts +34 -0
  67. package/dist/components/file-upload.d.ts.map +1 -0
  68. package/dist/components/file-upload.js +34 -0
  69. package/dist/components/gauge.d.ts +47 -0
  70. package/dist/components/gauge.d.ts.map +1 -0
  71. package/dist/components/gauge.js +65 -0
  72. package/dist/components/icon.d.ts +32 -0
  73. package/dist/components/icon.d.ts.map +1 -0
  74. package/dist/components/icon.js +20 -0
  75. package/dist/components/icons.d.ts +19 -0
  76. package/dist/components/icons.d.ts.map +1 -0
  77. package/dist/components/icons.js +39 -0
  78. package/dist/components/input.d.ts +35 -0
  79. package/dist/components/input.d.ts.map +1 -0
  80. package/dist/components/input.js +41 -0
  81. package/dist/components/layout.d.ts +133 -0
  82. package/dist/components/layout.d.ts.map +1 -0
  83. package/dist/components/layout.js +132 -0
  84. package/dist/components/link.d.ts +22 -0
  85. package/dist/components/link.d.ts.map +1 -0
  86. package/dist/components/link.js +2 -0
  87. package/dist/components/live-scope.d.ts +15 -0
  88. package/dist/components/live-scope.d.ts.map +1 -0
  89. package/dist/components/live-scope.js +67 -0
  90. package/dist/components/log-view.d.ts +34 -0
  91. package/dist/components/log-view.d.ts.map +1 -0
  92. package/dist/components/log-view.js +30 -0
  93. package/dist/components/number-input.d.ts +26 -0
  94. package/dist/components/number-input.d.ts.map +1 -0
  95. package/dist/components/number-input.js +57 -0
  96. package/dist/components/overlay.d.ts +257 -0
  97. package/dist/components/overlay.d.ts.map +1 -0
  98. package/dist/components/overlay.js +276 -0
  99. package/dist/components/pagination.d.ts +38 -0
  100. package/dist/components/pagination.d.ts.map +1 -0
  101. package/dist/components/pagination.js +46 -0
  102. package/dist/components/pie.d.ts +40 -0
  103. package/dist/components/pie.d.ts.map +1 -0
  104. package/dist/components/pie.js +74 -0
  105. package/dist/components/primitives.d.ts +96 -0
  106. package/dist/components/primitives.d.ts.map +1 -0
  107. package/dist/components/primitives.js +81 -0
  108. package/dist/components/progress.d.ts +29 -0
  109. package/dist/components/progress.d.ts.map +1 -0
  110. package/dist/components/progress.js +16 -0
  111. package/dist/components/radio-group.d.ts +17 -0
  112. package/dist/components/radio-group.d.ts.map +1 -0
  113. package/dist/components/radio-group.js +23 -0
  114. package/dist/components/rail-link.d.ts +24 -0
  115. package/dist/components/rail-link.d.ts.map +1 -0
  116. package/dist/components/rail-link.js +24 -0
  117. package/dist/components/rich-text.d.ts +51 -0
  118. package/dist/components/rich-text.d.ts.map +1 -0
  119. package/dist/components/rich-text.js +147 -0
  120. package/dist/components/section.d.ts +15 -0
  121. package/dist/components/section.d.ts.map +1 -0
  122. package/dist/components/section.js +8 -0
  123. package/dist/components/segmented.d.ts +13 -0
  124. package/dist/components/segmented.d.ts.map +1 -0
  125. package/dist/components/segmented.js +12 -0
  126. package/dist/components/skeleton.d.ts +69 -0
  127. package/dist/components/skeleton.d.ts.map +1 -0
  128. package/dist/components/skeleton.js +67 -0
  129. package/dist/components/slider.d.ts +18 -0
  130. package/dist/components/slider.d.ts.map +1 -0
  131. package/dist/components/slider.js +15 -0
  132. package/dist/components/sparkline.d.ts +17 -0
  133. package/dist/components/sparkline.d.ts.map +1 -0
  134. package/dist/components/sparkline.js +19 -0
  135. package/dist/components/spinner.d.ts +15 -0
  136. package/dist/components/spinner.d.ts.map +1 -0
  137. package/dist/components/spinner.js +22 -0
  138. package/dist/components/stacked-bar.d.ts +21 -0
  139. package/dist/components/stacked-bar.d.ts.map +1 -0
  140. package/dist/components/stacked-bar.js +41 -0
  141. package/dist/components/surface.d.ts +76 -0
  142. package/dist/components/surface.d.ts.map +1 -0
  143. package/dist/components/surface.js +49 -0
  144. package/dist/components/switch.d.ts +12 -0
  145. package/dist/components/switch.d.ts.map +1 -0
  146. package/dist/components/switch.js +13 -0
  147. package/dist/components/tabs.d.ts +47 -0
  148. package/dist/components/tabs.d.ts.map +1 -0
  149. package/dist/components/tabs.js +12 -0
  150. package/dist/components/timeline-strip.d.ts +19 -0
  151. package/dist/components/timeline-strip.d.ts.map +1 -0
  152. package/dist/components/timeline-strip.js +14 -0
  153. package/dist/components/tone.d.ts +58 -0
  154. package/dist/components/tone.d.ts.map +1 -0
  155. package/dist/components/tone.js +70 -0
  156. package/dist/components/tree-select.d.ts +26 -0
  157. package/dist/components/tree-select.d.ts.map +1 -0
  158. package/dist/components/tree-select.js +59 -0
  159. package/dist/index.d.ts +54 -0
  160. package/dist/index.d.ts.map +1 -0
  161. package/dist/index.js +70 -0
  162. package/dist/kit.css +2248 -0
  163. package/dist/lib/cn.d.ts +10 -0
  164. package/dist/lib/cn.d.ts.map +1 -0
  165. package/dist/lib/cn.js +12 -0
  166. package/dist/lib/color.d.ts +68 -0
  167. package/dist/lib/color.d.ts.map +1 -0
  168. package/dist/lib/color.js +134 -0
  169. package/dist/lib/palette.d.ts +84 -0
  170. package/dist/lib/palette.d.ts.map +1 -0
  171. package/dist/lib/palette.js +193 -0
  172. package/dist/lib/scroll-lock.d.ts +19 -0
  173. package/dist/lib/scroll-lock.d.ts.map +1 -0
  174. package/dist/lib/scroll-lock.js +33 -0
  175. package/dist/patterns/app-shell.d.ts +36 -0
  176. package/dist/patterns/app-shell.d.ts.map +1 -0
  177. package/dist/patterns/app-shell.js +14 -0
  178. package/dist/patterns/auth-template.d.ts +36 -0
  179. package/dist/patterns/auth-template.d.ts.map +1 -0
  180. package/dist/patterns/auth-template.js +40 -0
  181. package/dist/patterns/detail-template.d.ts +52 -0
  182. package/dist/patterns/detail-template.d.ts.map +1 -0
  183. package/dist/patterns/detail-template.js +11 -0
  184. package/dist/patterns/index.d.ts +18 -0
  185. package/dist/patterns/index.d.ts.map +1 -0
  186. package/dist/patterns/index.js +17 -0
  187. package/dist/patterns/list-template.d.ts +32 -0
  188. package/dist/patterns/list-template.d.ts.map +1 -0
  189. package/dist/patterns/list-template.js +8 -0
  190. package/dist/patterns/overview-template.d.ts +26 -0
  191. package/dist/patterns/overview-template.d.ts.map +1 -0
  192. package/dist/patterns/overview-template.js +8 -0
  193. package/dist/patterns/public-template.d.ts +28 -0
  194. package/dist/patterns/public-template.d.ts.map +1 -0
  195. package/dist/patterns/public-template.js +7 -0
  196. package/dist/patterns/settings-template.d.ts +50 -0
  197. package/dist/patterns/settings-template.d.ts.map +1 -0
  198. package/dist/patterns/settings-template.js +33 -0
  199. package/dist/patterns/shared.d.ts +40 -0
  200. package/dist/patterns/shared.d.ts.map +1 -0
  201. package/dist/patterns/shared.js +29 -0
  202. package/dist/patterns/wizard-template.d.ts +28 -0
  203. package/dist/patterns/wizard-template.d.ts.map +1 -0
  204. package/dist/patterns/wizard-template.js +15 -0
  205. package/dist/styles.css +5 -0
  206. package/dist/theme.css +812 -0
  207. 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
+ }