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,159 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* The keyboard behaviour Radix would have given us for free.
|
|
5
|
+
*
|
|
6
|
+
* We hand-rolled the overlays, so we owe the keyboard the same contract a
|
|
7
|
+
* library would have honoured. Two hooks, used by every overlay, so the
|
|
8
|
+
* behaviour cannot drift between them.
|
|
9
|
+
*/
|
|
10
|
+
const FOCUSABLE = [
|
|
11
|
+
"a[href]",
|
|
12
|
+
"button:not([disabled])",
|
|
13
|
+
"input:not([disabled])",
|
|
14
|
+
"select:not([disabled])",
|
|
15
|
+
"textarea:not([disabled])",
|
|
16
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
17
|
+
].join(",");
|
|
18
|
+
/**
|
|
19
|
+
* Visibility is judged by `hidden` and `aria-hidden`, deliberately not by
|
|
20
|
+
* `offsetParent`. offsetParent is null for anything inside a fixed-position
|
|
21
|
+
* ancestor in some engines — and always null under jsdom — so a trap built on
|
|
22
|
+
* it collapses to an empty list and silently stops trapping. The selector
|
|
23
|
+
* already excludes disabled controls and tabindex="-1".
|
|
24
|
+
*/
|
|
25
|
+
function focusable(root) {
|
|
26
|
+
return Array.from(root.querySelectorAll(FOCUSABLE)).filter((el) => !el.hasAttribute("hidden") && el.getAttribute("aria-hidden") !== "true");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Traps Tab inside a modal surface and gives it back on close.
|
|
30
|
+
*
|
|
31
|
+
* Without this a dialog looks modal and is not: Tab walks straight out into
|
|
32
|
+
* the page behind the scrim, where a keyboard user is stuck operating a page
|
|
33
|
+
* they cannot see. The scrim only stops the mouse.
|
|
34
|
+
*/
|
|
35
|
+
export function useFocusTrap(open) {
|
|
36
|
+
const ref = useRef(null);
|
|
37
|
+
const restoreTo = useRef(null);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!open)
|
|
40
|
+
return;
|
|
41
|
+
const root = ref.current;
|
|
42
|
+
if (!root)
|
|
43
|
+
return;
|
|
44
|
+
restoreTo.current = document.activeElement;
|
|
45
|
+
/* İLK KONTROL, ya da AÇIKÇA İSTENEN kontrol.
|
|
46
|
+
*
|
|
47
|
+
* Varsayılan olarak ilk odaklanabilir eleman odağı alıyor ve çoğu panelde
|
|
48
|
+
* doğrusu bu. Ama bir onay diyaloğunda ilk kontrol kapatma çarpısı, ikinci
|
|
49
|
+
* sıradaki ise "Sil": Enter'a basan biri kaydı siliyor. Yıkıcı bir
|
|
50
|
+
* diyalogda güvenli olan varsayılan olmalı.
|
|
51
|
+
*
|
|
52
|
+
* `autoFocus` NİTELİĞİ BURADA ÇALIŞMIYOR: React onu bağlarken bu etki
|
|
53
|
+
* henüz koşmamış oluyor, sonra trap odağı ilk elemana çekiyor ve niteliğin
|
|
54
|
+
* etkisi siliniyor. O yüzden mekanizma DOM üzerinden: panelin içinde
|
|
55
|
+
* `data-autofocus` taşıyan bir eleman varsa odak onun.
|
|
56
|
+
*
|
|
57
|
+
* `focusable` listesinden seçiliyor ki gizli ya da devre dışı bir elemana
|
|
58
|
+
* odak verilmesin: kapalı bir düğmeye odaklanmak, odağı hiç vermemekle
|
|
59
|
+
* aynı şey ama sessizce. */
|
|
60
|
+
const adaylar = focusable(root);
|
|
61
|
+
const istenen = adaylar.find((el) => el.hasAttribute("data-autofocus"));
|
|
62
|
+
const first = istenen ?? adaylar[0];
|
|
63
|
+
if (first)
|
|
64
|
+
first.focus();
|
|
65
|
+
else {
|
|
66
|
+
root.setAttribute("tabindex", "-1");
|
|
67
|
+
root.focus();
|
|
68
|
+
}
|
|
69
|
+
const onKey = (e) => {
|
|
70
|
+
if (e.key !== "Tab")
|
|
71
|
+
return;
|
|
72
|
+
const items = focusable(root);
|
|
73
|
+
if (items.length === 0) {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const first = items[0];
|
|
78
|
+
const last = items[items.length - 1];
|
|
79
|
+
const active = document.activeElement;
|
|
80
|
+
/* v5 compiles with noUncheckedIndexedAccess, which the prototype did not. The guards below
|
|
81
|
+
are not ceremony: `focusable()` genuinely returns [] when a dialog opens with everything
|
|
82
|
+
inside it still disabled, and the old code would have thrown on the first Tab. */
|
|
83
|
+
if (!first || !last)
|
|
84
|
+
return;
|
|
85
|
+
/* wrap at both ends, and pull focus back if it escaped some other way */
|
|
86
|
+
if (e.shiftKey && (active === first || !root.contains(active))) {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
last.focus();
|
|
89
|
+
}
|
|
90
|
+
else if (!e.shiftKey && (active === last || !root.contains(active))) {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
first.focus();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
document.addEventListener("keydown", onKey);
|
|
96
|
+
return () => {
|
|
97
|
+
document.removeEventListener("keydown", onKey);
|
|
98
|
+
/* give focus back to whatever opened this, not to the top of the page */
|
|
99
|
+
restoreTo.current?.focus?.();
|
|
100
|
+
};
|
|
101
|
+
}, [open]);
|
|
102
|
+
return ref;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Roving focus for a list of options — menus and selects.
|
|
106
|
+
*
|
|
107
|
+
* Arrow keys move, Home/End jump, Enter and Space choose, Escape closes.
|
|
108
|
+
* `data-tamga-option` marks the items so separators and labels are skipped
|
|
109
|
+
* without the hook needing to know what a separator is.
|
|
110
|
+
*/
|
|
111
|
+
export function useListKeys({ open, containerRef, onClose, }) {
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!open)
|
|
114
|
+
return;
|
|
115
|
+
const root = containerRef.current;
|
|
116
|
+
if (!root)
|
|
117
|
+
return;
|
|
118
|
+
const items = () => Array.from(root.querySelectorAll("[data-tamga-option]:not([disabled])"));
|
|
119
|
+
/* focus the first option on open, so the arrows have somewhere to start */
|
|
120
|
+
const initial = items();
|
|
121
|
+
initial[0]?.focus();
|
|
122
|
+
const onKey = (e) => {
|
|
123
|
+
const list = items();
|
|
124
|
+
if (list.length === 0)
|
|
125
|
+
return;
|
|
126
|
+
const i = list.indexOf(document.activeElement);
|
|
127
|
+
switch (e.key) {
|
|
128
|
+
case "ArrowDown":
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
list[i < 0 || i === list.length - 1 ? 0 : i + 1]?.focus();
|
|
131
|
+
break;
|
|
132
|
+
case "ArrowUp":
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
list[i <= 0 ? list.length - 1 : i - 1]?.focus();
|
|
135
|
+
break;
|
|
136
|
+
case "Home":
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
list[0]?.focus();
|
|
139
|
+
break;
|
|
140
|
+
case "End":
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
list[list.length - 1]?.focus();
|
|
143
|
+
break;
|
|
144
|
+
case "Escape":
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
onClose();
|
|
147
|
+
break;
|
|
148
|
+
case " ":
|
|
149
|
+
/* Enter fires a button natively; Space on a button does too, but only
|
|
150
|
+
on keyup — pre-empting it here keeps the two keys identical. */
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
document.activeElement?.click?.();
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
root.addEventListener("keydown", onKey);
|
|
157
|
+
return () => root.removeEventListener("keydown", onKey);
|
|
158
|
+
}, [open, containerRef, onClose]);
|
|
159
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parola alanı — göster/gizle düğmesiyle.
|
|
3
|
+
*
|
|
4
|
+
* Gerekçe: docs/gerekce/01-form-ve-girdi.md
|
|
5
|
+
*/
|
|
6
|
+
export declare function PasswordInput({ labels, className, ...props }: Omit<React.ComponentProps<"input">, "type"> & {
|
|
7
|
+
labels: {
|
|
8
|
+
show: string;
|
|
9
|
+
hide: string;
|
|
10
|
+
};
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
/**
|
|
13
|
+
* Maskeli sır — API anahtarı, webhook secret.
|
|
14
|
+
*
|
|
15
|
+
* `PasswordInput`'un kardeşi ama İŞİ TERS: parola alanına bir şey YAZILIR,
|
|
16
|
+
* buraya yazılmaz — okunur ve kopyalanır. O yüzden salt-okunur, ve asıl
|
|
17
|
+
* düğmesi kopyalama.
|
|
18
|
+
*
|
|
19
|
+
* Maske ilk ve son birkaç karakteri bırakıyor. Tamamen gizlemek, kullanıcının
|
|
20
|
+
* "hangi anahtar bu" sorusunu cevapsız bırakır; üç anahtarı olan biri
|
|
21
|
+
* hangisine baktığını bilemez.
|
|
22
|
+
*/
|
|
23
|
+
export declare function SecretField({ value, labels, visibleChars, className, }: {
|
|
24
|
+
value: string;
|
|
25
|
+
labels: {
|
|
26
|
+
reveal: string;
|
|
27
|
+
hide: string;
|
|
28
|
+
copy: string;
|
|
29
|
+
copied: string;
|
|
30
|
+
failed: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* How many characters stay visible at each end. TR: Baştan ve sondan açıkta kalan karakter
|
|
34
|
+
* sayısı.
|
|
35
|
+
*/
|
|
36
|
+
visibleChars?: number;
|
|
37
|
+
className?: string;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
/**
|
|
40
|
+
* Etiket girdisi.
|
|
41
|
+
*
|
|
42
|
+
* Enter ya da virgül bir etiketi kapatıyor; boşken Backspace sonuncuyu siliyor.
|
|
43
|
+
* İkincisi küçük görünür ama en çok kullanılan yoldur — yanlış yazılan bir
|
|
44
|
+
* etiketi silmek için fareye uzanmak, akışı kesen tek şeydir.
|
|
45
|
+
*
|
|
46
|
+
* Yinelenen ETİKET SESSİZCE YUTULUYOR, hata verilmiyor: aynı etiketi iki kez
|
|
47
|
+
* yazmak bir hata değil, bir tekrardır, ve kullanıcı zaten istediğini almış
|
|
48
|
+
* olur.
|
|
49
|
+
*/
|
|
50
|
+
export declare function TagsInput({ value, onChange, placeholder, labels, max, className, }: {
|
|
51
|
+
value: readonly string[];
|
|
52
|
+
onChange: (next: string[]) => void;
|
|
53
|
+
placeholder: string;
|
|
54
|
+
labels: {
|
|
55
|
+
remove: (tag: string) => string;
|
|
56
|
+
};
|
|
57
|
+
/** The ceiling; the input closes once it is full. TR: Üst sınır; dolduğunda girdi kapanır. */
|
|
58
|
+
max?: number;
|
|
59
|
+
className?: string;
|
|
60
|
+
}): import("react").JSX.Element;
|
|
61
|
+
/**
|
|
62
|
+
* Çoklu seçim — aranabilir.
|
|
63
|
+
*
|
|
64
|
+
* `Combobox` tek seçim yapar; bu birden çok. Ayrı bileşen olmasının sebebi
|
|
65
|
+
* görünüm değil DAVRANIŞ: tek seçimde liste seçince kapanır, çoklu seçimde
|
|
66
|
+
* KAPANMAZ — üç şey seçecek biri listeyi üç kez açmak zorunda kalmamalı.
|
|
67
|
+
*
|
|
68
|
+
* Seçilenler girdinin İÇİNDE çip olarak duruyor, altında ayrı bir listede
|
|
69
|
+
* değil: seçim ile seçilenler arasındaki mesafe arttıkça, kullanıcı neyi
|
|
70
|
+
* seçtiğini görmek için gözünü iki yere birden koymak zorunda kalır.
|
|
71
|
+
*/
|
|
72
|
+
export declare function MultiSelect({ options, value, onChange, placeholder, labels, className, }: {
|
|
73
|
+
options: readonly {
|
|
74
|
+
value: string;
|
|
75
|
+
label: string;
|
|
76
|
+
hint?: string;
|
|
77
|
+
}[];
|
|
78
|
+
value: readonly string[];
|
|
79
|
+
onChange: (next: string[]) => void;
|
|
80
|
+
placeholder: string;
|
|
81
|
+
labels: {
|
|
82
|
+
empty: string;
|
|
83
|
+
remove: (label: string) => string;
|
|
84
|
+
open: string;
|
|
85
|
+
};
|
|
86
|
+
className?: string;
|
|
87
|
+
}): import("react").JSX.Element;
|
|
88
|
+
/**
|
|
89
|
+
* Zamanlama girdisi — "her N dakikada bir".
|
|
90
|
+
*
|
|
91
|
+
* NEDEN CRON DEĞİL. Bir cron ifadesi (yıldız-eğik-beş biçimi) bir geliştirici
|
|
92
|
+
* için okunur, bir panel
|
|
93
|
+
* kullanıcısı için değildir — ve yanlış yazılan bir cron ifadesi hata
|
|
94
|
+
* vermez, sadece yanlış zamanda çalışır. Bu bileşen kürasyonlu bir liste
|
|
95
|
+
* sunuyor: kürasyonlu seçenek, serbestlik değil.
|
|
96
|
+
*
|
|
97
|
+
* Aralıklar dakika olarak veriliyor, çevirisi çağıranın: "5 dakika" ile
|
|
98
|
+
* "5 minutes" arasındaki farkı kit bilemez.
|
|
99
|
+
*/
|
|
100
|
+
export declare function ScheduleInput({ value, onChange, options, label, className, }: {
|
|
101
|
+
/** The interval, in minutes. TR: Dakika cinsinden aralık. */
|
|
102
|
+
value: number;
|
|
103
|
+
onChange: (minutes: number) => void;
|
|
104
|
+
/**
|
|
105
|
+
* `[{ minutes: 5, label: "5 minutes" }, …]`: ordered and curated. TR: `[{ minutes: 5, label:
|
|
106
|
+
* "5 dakika" }, …]`: sıralı ve kürasyonlu.
|
|
107
|
+
*/
|
|
108
|
+
options: readonly {
|
|
109
|
+
minutes: number;
|
|
110
|
+
label: string;
|
|
111
|
+
}[];
|
|
112
|
+
label: string;
|
|
113
|
+
className?: string;
|
|
114
|
+
}): import("react").JSX.Element;
|
|
115
|
+
//# sourceMappingURL=advanced-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advanced-input.d.ts","sourceRoot":"","sources":["../../src/components/advanced-input.tsx"],"names":[],"mappings":"AAcA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,GAAG;IAC/C,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC,+BAwBA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,MAAM,EACN,YAAgB,EAChB,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA4DA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,WAAW,EACX,MAAM,EACN,GAAG,EACH,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAC;IAC5C,8FAA8F;IAC9F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAgDA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAAW,EACX,MAAM,EACN,SAAS,GACV,EAAE;IACD,OAAO,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACpE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAuJA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,EACL,SAAS,GACV,EAAE;IACD,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;OAGG;IACH,OAAO,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAeA"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Icon } from "./icon.js";
|
|
6
|
+
import { Check, Close, Customize, Eye, EyeSlash, Search } from "./icons.js";
|
|
7
|
+
/* ------------------------------------------------------------------ *
|
|
8
|
+
* Gelişmiş girdiler.
|
|
9
|
+
*
|
|
10
|
+
* Hepsinin ortak yanı: bir `<input>` ile yapılabilir GÖRÜNMESİ, ve
|
|
11
|
+
* yapılamaması. Her biri tek bir somut hatanın karşılığı.
|
|
12
|
+
* ------------------------------------------------------------------ */
|
|
13
|
+
/**
|
|
14
|
+
* Parola alanı — göster/gizle düğmesiyle.
|
|
15
|
+
*
|
|
16
|
+
* Gerekçe: docs/gerekce/01-form-ve-girdi.md
|
|
17
|
+
*/
|
|
18
|
+
export function PasswordInput({ labels, className, ...props }) {
|
|
19
|
+
const [shown, setShown] = useState(false);
|
|
20
|
+
return (_jsxs("span", { className: "relative flex items-center", children: [_jsx("input", { ...props, type: shown ? "text" : "password", className: cn("tamga-input w-full pr-10", className) }), _jsx("button", { type: "button", className: "tamga-mini-btn absolute right-1.5", "aria-label": shown ? labels.hide : labels.show, "aria-pressed": shown, onClick: () => setShown((v) => !v), children: _jsx(Icon, { icon: shown ? EyeSlash : Eye, size: "xs" }) })] }));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Maskeli sır — API anahtarı, webhook secret.
|
|
24
|
+
*
|
|
25
|
+
* `PasswordInput`'un kardeşi ama İŞİ TERS: parola alanına bir şey YAZILIR,
|
|
26
|
+
* buraya yazılmaz — okunur ve kopyalanır. O yüzden salt-okunur, ve asıl
|
|
27
|
+
* düğmesi kopyalama.
|
|
28
|
+
*
|
|
29
|
+
* Maske ilk ve son birkaç karakteri bırakıyor. Tamamen gizlemek, kullanıcının
|
|
30
|
+
* "hangi anahtar bu" sorusunu cevapsız bırakır; üç anahtarı olan biri
|
|
31
|
+
* hangisine baktığını bilemez.
|
|
32
|
+
*/
|
|
33
|
+
export function SecretField({ value, labels, visibleChars = 4, className, }) {
|
|
34
|
+
const [shown, setShown] = useState(false);
|
|
35
|
+
const [copied, setCopied] = useState("idle");
|
|
36
|
+
const timer = useRef(undefined);
|
|
37
|
+
/* Zamanlayıcı bir ref'te tutuluyor ve her tıklamada sıfırlanıyor.
|
|
38
|
+
|
|
39
|
+
Öncesi çıplak bir `setTimeout`'tu ve iki şeyi birden bozuyordu: art arda
|
|
40
|
+
iki tıklamada ilk zamanlayıcı hâlâ çalıştığı için etiket erken geri
|
|
41
|
+
dönüyordu, ve bileşen 1.6 saniye içinde sökülürse zamanlayıcı ölü bir
|
|
42
|
+
bileşene yazmaya çalışıyordu. */
|
|
43
|
+
useEffect(() => () => window.clearTimeout(timer.current), []);
|
|
44
|
+
const masked = value.length <= visibleChars * 2
|
|
45
|
+
? "•".repeat(value.length)
|
|
46
|
+
: `${value.slice(0, visibleChars)}${"•".repeat(8)}${value.slice(-visibleChars)}`;
|
|
47
|
+
async function copy() {
|
|
48
|
+
try {
|
|
49
|
+
await navigator.clipboard.writeText(value);
|
|
50
|
+
setCopied("done");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
setCopied("failed");
|
|
54
|
+
}
|
|
55
|
+
window.clearTimeout(timer.current);
|
|
56
|
+
timer.current = window.setTimeout(() => setCopied("idle"), 1600);
|
|
57
|
+
}
|
|
58
|
+
return (_jsxs("span", { className: cn("relative flex items-center", className), children: [_jsx("input", { readOnly: true, value: shown ? value : masked, className: "tamga-input w-full pr-18 font-mono",
|
|
59
|
+
/* Salt-okunur bir alan yine de odaklanabilir olmalı: kullanıcı onu
|
|
60
|
+
seçip elle kopyalayabilsin. `disabled` bunu imkânsız kılardı. */
|
|
61
|
+
onFocus: (e) => e.currentTarget.select() }), _jsxs("span", { className: "absolute right-1.5 flex gap-0.5", children: [_jsx("button", { type: "button", className: "tamga-mini-btn", "aria-label": shown ? labels.hide : labels.reveal, "aria-pressed": shown, onClick: () => setShown((v) => !v), children: _jsx(Icon, { icon: shown ? EyeSlash : Eye, size: "xs" }) }), _jsx("button", { type: "button", className: "tamga-mini-btn", "aria-label": copied === "done" ? labels.copied : copied === "failed" ? labels.failed : labels.copy, onClick: copy, children: _jsx(Icon, { icon: copied === "done" ? Check : Customize, size: "xs" }) })] })] }));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Etiket girdisi.
|
|
65
|
+
*
|
|
66
|
+
* Enter ya da virgül bir etiketi kapatıyor; boşken Backspace sonuncuyu siliyor.
|
|
67
|
+
* İkincisi küçük görünür ama en çok kullanılan yoldur — yanlış yazılan bir
|
|
68
|
+
* etiketi silmek için fareye uzanmak, akışı kesen tek şeydir.
|
|
69
|
+
*
|
|
70
|
+
* Yinelenen ETİKET SESSİZCE YUTULUYOR, hata verilmiyor: aynı etiketi iki kez
|
|
71
|
+
* yazmak bir hata değil, bir tekrardır, ve kullanıcı zaten istediğini almış
|
|
72
|
+
* olur.
|
|
73
|
+
*/
|
|
74
|
+
export function TagsInput({ value, onChange, placeholder, labels, max, className, }) {
|
|
75
|
+
const [draft, setDraft] = useState("");
|
|
76
|
+
const full = max !== undefined && value.length >= max;
|
|
77
|
+
function commit() {
|
|
78
|
+
const tag = draft.trim();
|
|
79
|
+
setDraft("");
|
|
80
|
+
if (!tag || value.includes(tag) || full)
|
|
81
|
+
return;
|
|
82
|
+
onChange([...value, tag]);
|
|
83
|
+
}
|
|
84
|
+
return (_jsxs("span", { className: cn("tamga-input h-auto min-h-10 w-full flex-wrap items-center gap-1.5 py-1.5", className), style: { display: "flex" }, children: [value.map((tag) => (_jsxs("span", { className: "tamga-chip gap-1", children: [tag, _jsx("button", { type: "button", "aria-label": labels.remove(tag), onClick: () => onChange(value.filter((x) => x !== tag)), className: "-mr-1 inline-flex items-center", children: _jsx(Icon, { icon: Close, size: "xs" }) })] }, tag))), !full ? (_jsx("input", { value: draft, placeholder: value.length === 0 ? placeholder : undefined, onChange: (e) => setDraft(e.target.value), onBlur: commit, onKeyDown: (e) => {
|
|
85
|
+
if (e.key === "Enter" || e.key === ",") {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
commit();
|
|
88
|
+
}
|
|
89
|
+
else if (e.key === "Backspace" && draft === "" && value.length > 0) {
|
|
90
|
+
onChange(value.slice(0, -1));
|
|
91
|
+
}
|
|
92
|
+
}, className: "min-w-24 flex-1 bg-transparent text-body text-ink outline-none" })) : null] }));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Çoklu seçim — aranabilir.
|
|
96
|
+
*
|
|
97
|
+
* `Combobox` tek seçim yapar; bu birden çok. Ayrı bileşen olmasının sebebi
|
|
98
|
+
* görünüm değil DAVRANIŞ: tek seçimde liste seçince kapanır, çoklu seçimde
|
|
99
|
+
* KAPANMAZ — üç şey seçecek biri listeyi üç kez açmak zorunda kalmamalı.
|
|
100
|
+
*
|
|
101
|
+
* Seçilenler girdinin İÇİNDE çip olarak duruyor, altında ayrı bir listede
|
|
102
|
+
* değil: seçim ile seçilenler arasındaki mesafe arttıkça, kullanıcı neyi
|
|
103
|
+
* seçtiğini görmek için gözünü iki yere birden koymak zorunda kalır.
|
|
104
|
+
*/
|
|
105
|
+
export function MultiSelect({ options, value, onChange, placeholder, labels, className, }) {
|
|
106
|
+
const id = useId();
|
|
107
|
+
const [open, setOpen] = useState(false);
|
|
108
|
+
const [query, setQuery] = useState("");
|
|
109
|
+
const box = useRef(null);
|
|
110
|
+
const [active, setActive] = useState(0);
|
|
111
|
+
const shown = options.filter((o) => !query || o.label.toLocaleLowerCase().includes(query.toLocaleLowerCase()));
|
|
112
|
+
const chosen = options.filter((o) => value.includes(o.value));
|
|
113
|
+
function toggle(v) {
|
|
114
|
+
onChange(value.includes(v) ? value.filter((x) => x !== v) : [...value, v]);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* KLAVYE, ve onsuz bu kontrol yarım.
|
|
118
|
+
*
|
|
119
|
+
* Etiket eklemenin doğal yolu YAZIP ENTER'A BASMAK: kullanıcı "adidas" yazıp
|
|
120
|
+
* Enter, "nike" yazıp Enter diyor ve iki çip ekliyor. Önce yalnız fareyle
|
|
121
|
+
* çalışıyordu; yazdıktan sonra listeye uzanıp tıklamak gerekiyordu, ve
|
|
122
|
+
* klavyeyle gezen biri seçim yapamıyordu.
|
|
123
|
+
*
|
|
124
|
+
* Enter vurgulanan seçeneği ekler/çıkarır, ve kutuyu boşaltır
|
|
125
|
+
* Ok tuşları vurguyu gezdirir
|
|
126
|
+
* Backspace kutu BOŞKEN son çipi kaldırır (yazarken silmeye karışmaz)
|
|
127
|
+
* Escape listeyi kapatır
|
|
128
|
+
*/
|
|
129
|
+
function tus(e) {
|
|
130
|
+
if (e.key === "ArrowDown") {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
setOpen(true);
|
|
133
|
+
setActive((i) => Math.min(i + 1, shown.length - 1));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (e.key === "ArrowUp") {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
setActive((i) => Math.max(i - 1, 0));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (e.key === "Enter") {
|
|
142
|
+
const o = shown[active] ?? shown[0];
|
|
143
|
+
if (!o)
|
|
144
|
+
return;
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
toggle(o.value);
|
|
147
|
+
setQuery("");
|
|
148
|
+
setActive(0);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (e.key === "Backspace" && query === "" && value.length > 0) {
|
|
152
|
+
/* Yazarken Backspace harf siler; yalnız kutu boşken çipe dokunuyor. */
|
|
153
|
+
e.preventDefault();
|
|
154
|
+
onChange(value.slice(0, -1));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (e.key === "Escape")
|
|
158
|
+
setOpen(false);
|
|
159
|
+
}
|
|
160
|
+
return (_jsxs("div", { ref: box, className: cn("relative", className),
|
|
161
|
+
/* Odak kutunun DIŞINA çıkınca kapanıyor — tıklama dinleyicisiyle değil.
|
|
162
|
+
Dışarı tıklamayı dinlemek klavyeyle çıkanı görmez ve liste açık kalır. */
|
|
163
|
+
onBlur: (e) => {
|
|
164
|
+
if (!box.current?.contains(e.relatedTarget))
|
|
165
|
+
setOpen(false);
|
|
166
|
+
}, children: [_jsxs("span", { className: "tamga-input relative h-auto min-h-10 w-full flex-wrap items-center gap-1.5 py-1.5", "data-leading": "true", style: { display: "flex" }, children: [_jsx(Icon, { icon: Search, size: "xs", "aria-hidden": true, className: "pointer-events-none absolute left-3 top-3 text-ink-faint" }), chosen.map((o) => (_jsxs("span", { className: "tamga-chip gap-1", children: [o.label, _jsx("button", { type: "button", "aria-label": labels.remove(o.label), onClick: () => toggle(o.value), className: "-mr-1 inline-flex items-center", children: _jsx(Icon, { icon: Close, size: "xs" }) })] }, o.value))), _jsx("input", { value: query, placeholder: chosen.length === 0 ? placeholder : undefined, onChange: (e) => {
|
|
167
|
+
setQuery(e.target.value);
|
|
168
|
+
setActive(0);
|
|
169
|
+
setOpen(true);
|
|
170
|
+
}, onKeyDown: tus, onFocus: () => setOpen(true), role: "combobox", "aria-expanded": open, "aria-controls": id, "aria-label": labels.open, className: "min-w-24 flex-1 bg-transparent text-body text-ink outline-none" })] }), open ? (_jsx("div", { id: id, role: "listbox", "aria-multiselectable": true, className: "tamga-overlay absolute z-30 mt-2 max-h-64 w-full overflow-y-auto py-1", children: shown.length === 0 ? (_jsx("p", { className: "px-4 py-3 text-small text-ink-faint", children: labels.empty })) : (shown.map((o, i) => {
|
|
171
|
+
const on = value.includes(o.value);
|
|
172
|
+
return (_jsxs("button", { type: "button", role: "option", "aria-selected": on, "data-selected": on, "data-active": i === active || undefined, className: "tamga-option w-full", onMouseEnter: () => setActive(i), onClick: () => toggle(o.value), children: [_jsx("span", { className: "flex-1 text-left", children: o.label }), o.hint ? _jsx("span", { className: "font-mono text-caption text-ink-faint", children: o.hint }) : null, on ? _jsx(Icon, { icon: Check, size: "xs" }) : null] }, o.value));
|
|
173
|
+
})) })) : null] }));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Zamanlama girdisi — "her N dakikada bir".
|
|
177
|
+
*
|
|
178
|
+
* NEDEN CRON DEĞİL. Bir cron ifadesi (yıldız-eğik-beş biçimi) bir geliştirici
|
|
179
|
+
* için okunur, bir panel
|
|
180
|
+
* kullanıcısı için değildir — ve yanlış yazılan bir cron ifadesi hata
|
|
181
|
+
* vermez, sadece yanlış zamanda çalışır. Bu bileşen kürasyonlu bir liste
|
|
182
|
+
* sunuyor: kürasyonlu seçenek, serbestlik değil.
|
|
183
|
+
*
|
|
184
|
+
* Aralıklar dakika olarak veriliyor, çevirisi çağıranın: "5 dakika" ile
|
|
185
|
+
* "5 minutes" arasındaki farkı kit bilemez.
|
|
186
|
+
*/
|
|
187
|
+
export function ScheduleInput({ value, onChange, options, label, className, }) {
|
|
188
|
+
return (_jsx("select", { className: cn("tamga-input w-full", className), "aria-label": label, value: value, onChange: (e) => onChange(Number(e.target.value)), children: options.map((o) => (_jsx("option", { value: o.minutes, children: o.label }, o.minutes))) }));
|
|
189
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Square, monochrome. Identity is text — color is reserved for health. */
|
|
2
|
+
export declare function Avatar({ name, size, src, bare, letters, }: {
|
|
3
|
+
name: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
/**
|
|
6
|
+
* falls back to initials when absent or broken TR: yoksa ya da yüklenemezse baş harflere
|
|
7
|
+
* düşer
|
|
8
|
+
*/
|
|
9
|
+
src?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Drop the avatar's own frame. For when it sits INSIDE a control that already has one: a
|
|
12
|
+
* bordered tile inside a bordered button is a box in a box, and it made the account control
|
|
13
|
+
* the odd one out on a toolbar where every other button holds a bare glyph. TR: Avatarın
|
|
14
|
+
* kendi çerçevesini kaldır. Zaten çerçevesi olan bir kontrolün İÇİNDE duruyorsa: çerçeveli
|
|
15
|
+
* bir düğmenin içindeki çerçeveli bir karo kutu içinde kutudur, ve hesap kontrolünü her
|
|
16
|
+
* düğmesi çıplak bir simge taşıyan bir araç çubuğunda tek başına farklı gösteriyordu.
|
|
17
|
+
*/
|
|
18
|
+
bare?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* How many initials to show. An avatar standing alone carries two, the least it takes to
|
|
21
|
+
* point at a person. But two do not fit in an OVERLAPPING stack: the tile above clips the
|
|
22
|
+
* right of the one below and "BS" reads as "B" plus half an "S". A photo does not have this
|
|
23
|
+
* problem, because half a face is still a face; half a letter cannot be read. `AvatarStack`
|
|
24
|
+
* passes 1 for exactly that reason. TR: Kaç baş harf gösterileceği. Tek başına duran bir
|
|
25
|
+
* avatar iki harf taşır; bir kişiyi işaret etmek için gereken en az şey o. Ama ÖRTÜŞEN bir
|
|
26
|
+
* yığında iki harf sığmaz: üstteki karo alttakinin sağını keser ve "BS", "B" ile yarım bir
|
|
27
|
+
* "S" olarak okunur. Fotoğrafta bu sorun değildir, çünkü yarım bir yüz hâlâ bir yüzdür; bir
|
|
28
|
+
* harf ise yarım okunamaz. `AvatarStack` bu yüzden 1 geçiyor.
|
|
29
|
+
*/
|
|
30
|
+
letters?: 1 | 2;
|
|
31
|
+
}): import("react").JSX.Element;
|
|
32
|
+
/**
|
|
33
|
+
* Üst üste binen ekip.
|
|
34
|
+
*
|
|
35
|
+
* İKİ ŞEY AYRI AYRI YANLIŞTI ve ikisi de aynı sonucu veriyordu: karolar
|
|
36
|
+
* birbirine yapışıp tek bir karışık blok olarak okunuyordu.
|
|
37
|
+
*
|
|
38
|
+
* Birincisi ÖRTÜŞME SABİTTİ — her boyutta 8px. Bir 24px karoda bu üçte bir
|
|
39
|
+
* demek, ve iki baş harften biri kapanıyor. Şimdi boyuna oranlı: dörtte bir,
|
|
40
|
+
* yani karo büyüdükçe örtüşme de büyüyor ama oran sabit kalıyor.
|
|
41
|
+
*
|
|
42
|
+
* İkincisi AYIRICI HALKA YOKTU. Avatarın kendi 1px kenarı yeterli sanılmıştı
|
|
43
|
+
* ve değildi: iki karo bitişince o kenarlar tek bir çizgi oluyor, ve
|
|
44
|
+
* hangisinin önde olduğu okunmuyor. Üstteki karonun etrafına ARKA PLAN
|
|
45
|
+
* RENGİNDE bir halka koyunca, üstteki alttakini gerçekten kesiyor.
|
|
46
|
+
*
|
|
47
|
+
* `surface` bu yüzden bir prop: halka, yığının ÜSTÜNDE DURDUĞU zeminin rengi
|
|
48
|
+
* olmalı. Varsayılan kart zemini (`--color-shell`), çünkü bir ekip listesi
|
|
49
|
+
* neredeyse her zaman bir kartın içindedir — ama sayfa zemininde duracaksa
|
|
50
|
+
* çağıran onu söyler. Bulanıklık yok: `0 0 0` yayılımsız bir halka, gölge
|
|
51
|
+
* değil.
|
|
52
|
+
*/
|
|
53
|
+
export declare function AvatarStack({ names, extra, size, surface, }: {
|
|
54
|
+
names: string[];
|
|
55
|
+
extra?: number;
|
|
56
|
+
size?: number;
|
|
57
|
+
/**
|
|
58
|
+
* The colour of the ground behind the stack; the separating ring is drawn in it. TR: Yığının
|
|
59
|
+
* arkasındaki zeminin rengi; ayırıcı halka bu renkte çizilir.
|
|
60
|
+
*/
|
|
61
|
+
surface?: string;
|
|
62
|
+
}): import("react").JSX.Element;
|
|
63
|
+
//# sourceMappingURL=avatar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../src/components/avatar.tsx"],"names":[],"mappings":"AAUA,2EAA2E;AAC3E,wBAAgB,MAAM,CAAC,EACrB,IAAI,EACJ,IAAS,EACT,GAAG,EACH,IAAY,EACZ,OAAW,GACZ,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CACjB,+BAiCA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,IAAS,EACT,OAA8B,GAC/B,EAAE;IACD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,+BA4BA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
function initials(name, count) {
|
|
4
|
+
return name
|
|
5
|
+
.split(" ")
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.slice(0, count)
|
|
8
|
+
.map((p) => p[0]?.toUpperCase())
|
|
9
|
+
.join("");
|
|
10
|
+
}
|
|
11
|
+
/** Square, monochrome. Identity is text — color is reserved for health. */
|
|
12
|
+
export function Avatar({ name, size = 32, src, bare = false, letters = 2, }) {
|
|
13
|
+
const box = {
|
|
14
|
+
width: size,
|
|
15
|
+
height: size,
|
|
16
|
+
/* 0.42, not 0.34: at the old ratio a 26px avatar produced 9px initials, which read as a
|
|
17
|
+
smudge next to a 16px icon glyph. Initials ARE the glyph when an avatar sits in a control. */
|
|
18
|
+
fontSize: Math.max(11, Math.round(size * 0.42)),
|
|
19
|
+
};
|
|
20
|
+
if (src) {
|
|
21
|
+
return (_jsx("img", { src: src, alt: name, width: size, height: size, className: cn("shrink-0 object-cover", !bare && "rounded-[var(--radius-mark)] border border-edge"), style: box }));
|
|
22
|
+
}
|
|
23
|
+
return (_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center font-semibold text-ink", !bare && "rounded-[var(--radius-mark)] border border-edge bg-hover"), style: box, "aria-hidden": true, children: initials(name, letters) }));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Üst üste binen ekip.
|
|
27
|
+
*
|
|
28
|
+
* İKİ ŞEY AYRI AYRI YANLIŞTI ve ikisi de aynı sonucu veriyordu: karolar
|
|
29
|
+
* birbirine yapışıp tek bir karışık blok olarak okunuyordu.
|
|
30
|
+
*
|
|
31
|
+
* Birincisi ÖRTÜŞME SABİTTİ — her boyutta 8px. Bir 24px karoda bu üçte bir
|
|
32
|
+
* demek, ve iki baş harften biri kapanıyor. Şimdi boyuna oranlı: dörtte bir,
|
|
33
|
+
* yani karo büyüdükçe örtüşme de büyüyor ama oran sabit kalıyor.
|
|
34
|
+
*
|
|
35
|
+
* İkincisi AYIRICI HALKA YOKTU. Avatarın kendi 1px kenarı yeterli sanılmıştı
|
|
36
|
+
* ve değildi: iki karo bitişince o kenarlar tek bir çizgi oluyor, ve
|
|
37
|
+
* hangisinin önde olduğu okunmuyor. Üstteki karonun etrafına ARKA PLAN
|
|
38
|
+
* RENGİNDE bir halka koyunca, üstteki alttakini gerçekten kesiyor.
|
|
39
|
+
*
|
|
40
|
+
* `surface` bu yüzden bir prop: halka, yığının ÜSTÜNDE DURDUĞU zeminin rengi
|
|
41
|
+
* olmalı. Varsayılan kart zemini (`--color-shell`), çünkü bir ekip listesi
|
|
42
|
+
* neredeyse her zaman bir kartın içindedir — ama sayfa zemininde duracaksa
|
|
43
|
+
* çağıran onu söyler. Bulanıklık yok: `0 0 0` yayılımsız bir halka, gölge
|
|
44
|
+
* değil.
|
|
45
|
+
*/
|
|
46
|
+
export function AvatarStack({ names, extra, size = 24, surface = "var(--color-shell)", }) {
|
|
47
|
+
const overlap = Math.round(size / 4);
|
|
48
|
+
const ring = { boxShadow: `0 0 0 2px ${surface}` };
|
|
49
|
+
/* Karoların kendi baş harfleri `aria-hidden` — üst üste binmiş harfler ekran
|
|
50
|
+
okuyucuda anlamsız bir dizi olurdu. Adlar bir kez, düz metin olarak
|
|
51
|
+
veriliyor: "Berika Sultan, Deniz Kara ve 7 kişi daha". */
|
|
52
|
+
const spoken = [...names, extra ? `+${extra}` : null].filter(Boolean).join(", ");
|
|
53
|
+
return (_jsxs("div", { className: "flex items-center", role: "img", "aria-label": spoken, children: [names.map((n, i) => (_jsx("span", { className: "rounded-[var(--radius-mark)]", style: { marginLeft: i === 0 ? 0 : -overlap, ...ring }, children: _jsx(Avatar, { name: n, size: size, letters: 1 }) }, n + i))), extra ? (_jsxs("span", { className: "inline-flex items-center justify-center rounded-[var(--radius-mark)] border border-edge bg-shell font-mono text-caption font-bold text-ink", style: { width: size, height: size, marginLeft: -overlap, ...ring }, children: ["+", extra] })) : null] }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Tone } from "./tone.js";
|
|
2
|
+
export declare function StatusChip({ label, state, dot, live, mono, severity, }: {
|
|
3
|
+
label: string;
|
|
4
|
+
state: Tone;
|
|
5
|
+
dot?: boolean;
|
|
6
|
+
live?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* identifier-shaped labels (codes, short keys) keep the mono face TR: tanımlayıcı biçimli
|
|
9
|
+
* etiketler (kodlar, kısa anahtarlar) mono yüzü korur
|
|
10
|
+
*/
|
|
11
|
+
mono?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* explicit rank, once the API carries one; otherwise derived from state TR: API bir sıra
|
|
14
|
+
* taşıdığında açık sıra; yoksa durumdan türetiliyor
|
|
15
|
+
*/
|
|
16
|
+
severity?: number | null;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export declare function Delta({ value, better, mono, }: {
|
|
19
|
+
value: string;
|
|
20
|
+
better: boolean;
|
|
21
|
+
mono?: boolean;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
23
|
+
/**
|
|
24
|
+
* Solid square status mark used in dense tables. Uses `fg` rather than `mark`
|
|
25
|
+
* so a single 10px square still reads at rest — bars can afford to be quieter,
|
|
26
|
+
* a lone dot cannot.
|
|
27
|
+
*/
|
|
28
|
+
export declare function Dot({ state, live, severity, }: {
|
|
29
|
+
state: Tone;
|
|
30
|
+
live?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* explicit rank, once the API carries one; otherwise derived from state TR: API bir sıra
|
|
33
|
+
* taşıdığında açık sıra; yoksa durumdan türetiliyor
|
|
34
|
+
*/
|
|
35
|
+
severity?: number | null;
|
|
36
|
+
}): import("react").JSX.Element;
|
|
37
|
+
//# sourceMappingURL=badge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge.d.ts","sourceRoot":"","sources":["../../src/components/badge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAStC,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,GAAU,EACV,IAAY,EACZ,IAAY,EACZ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,+BAiBA;AAED,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,MAAM,EACN,IAAW,GACZ,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,+BAeA;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,EAClB,KAAK,EACL,IAAY,EACZ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,+BAUA"}
|