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,276 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { toneOf } from "./tone.js";
|
|
5
|
+
import { Icon } from "./icon.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import { Close } from "./icons.js";
|
|
8
|
+
import { SkeletonOptions, SkeletonPanel } from "./skeleton.js";
|
|
9
|
+
import { Button } from "./button.js";
|
|
10
|
+
import { useFocusTrap, useListKeys } from "./a11y.js";
|
|
11
|
+
import { MiniButton } from "./button.js";
|
|
12
|
+
import { useScrollLock } from "../lib/scroll-lock.js";
|
|
13
|
+
/* ------------------------------------------------------------------ *
|
|
14
|
+
* The overlay plane. Everything here sits at 6px — past the 4px the
|
|
15
|
+
* page can reach — which is the whole "this is floating" signal.
|
|
16
|
+
* Only Dialog and Sheet add a scrim. Tooltip is the one exception:
|
|
17
|
+
* it is a label, not a surface you act on, so it never lifts.
|
|
18
|
+
* ------------------------------------------------------------------ */
|
|
19
|
+
function useDismiss(open, close) {
|
|
20
|
+
const box = useRef(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!open)
|
|
23
|
+
return;
|
|
24
|
+
const away = (e) => {
|
|
25
|
+
if (box.current && !box.current.contains(e.target))
|
|
26
|
+
close();
|
|
27
|
+
};
|
|
28
|
+
const esc = (e) => e.key === "Escape" && close();
|
|
29
|
+
document.addEventListener("mousedown", away);
|
|
30
|
+
document.addEventListener("keydown", esc);
|
|
31
|
+
return () => {
|
|
32
|
+
document.removeEventListener("mousedown", away);
|
|
33
|
+
document.removeEventListener("keydown", esc);
|
|
34
|
+
};
|
|
35
|
+
}, [open, close]);
|
|
36
|
+
return box;
|
|
37
|
+
}
|
|
38
|
+
export function DropdownMenu({ trigger, items, align = "start", width = 200, loading = false, loadingRows = 4, openOnHover = false, }) {
|
|
39
|
+
const [open, setOpen] = useState(false);
|
|
40
|
+
const box = useDismiss(open, () => setOpen(false));
|
|
41
|
+
const close = useCallback(() => setOpen(false), []);
|
|
42
|
+
const panel = useRef(null);
|
|
43
|
+
useListKeys({ open: open && !loading, containerRef: panel, onClose: close });
|
|
44
|
+
/* KAPANIŞ GECİKMELİ, ANINDA DEĞİL. Tetikleyici ile panel arasında birkaç
|
|
45
|
+
piksellik bir boşluk var ve işaretçi oradan geçerken `pointerleave`
|
|
46
|
+
tetikleniyor: menü tam üstüne gidilirken kapanıyordu. 120ms, o boşluğu
|
|
47
|
+
geçmeye yeten ve "takılı kaldı" hissi vermeyen aralık. */
|
|
48
|
+
const kapatmaSayaci = useRef(null);
|
|
49
|
+
const gecikmeliKapat = useCallback(() => {
|
|
50
|
+
if (!openOnHover)
|
|
51
|
+
return;
|
|
52
|
+
kapatmaSayaci.current = window.setTimeout(() => setOpen(false), 120);
|
|
53
|
+
}, [openOnHover]);
|
|
54
|
+
const kapatmayiIptal = useCallback(() => {
|
|
55
|
+
if (kapatmaSayaci.current !== null) {
|
|
56
|
+
window.clearTimeout(kapatmaSayaci.current);
|
|
57
|
+
kapatmaSayaci.current = null;
|
|
58
|
+
}
|
|
59
|
+
}, []);
|
|
60
|
+
useEffect(() => () => kapatmayiIptal(), [kapatmayiIptal]);
|
|
61
|
+
return (_jsxs("div", { ref: box, className: "relative inline-flex", onPointerEnter: openOnHover
|
|
62
|
+
? (e) => {
|
|
63
|
+
/* YALNIZ FARE. Dokunmatik bir ekranda `pointerenter` dokunmayla
|
|
64
|
+
birlikte geliyor ve hemen ardından gelen `click` menüyü kapatıyor:
|
|
65
|
+
kullanıcı dokunuyor, menü açılıp anında kapanıyor. */
|
|
66
|
+
if (e.pointerType !== "mouse")
|
|
67
|
+
return;
|
|
68
|
+
kapatmayiIptal();
|
|
69
|
+
setOpen(true);
|
|
70
|
+
}
|
|
71
|
+
: undefined, onPointerLeave: openOnHover ? gecikmeliKapat : undefined, children: [_jsx("span", { onClick: () => setOpen((o) => !o), children: trigger }), open && (_jsxs("div", { ref: panel, role: "menu", className: "tamga-overlay absolute top-[var(--overlay-below)] z-40 overflow-hidden py-1", style: { width, [align === "end" ? "right" : "left"]: 0 }, "aria-busy": loading || undefined, children: [loading && _jsx(SkeletonOptions, { rows: loadingRows }), !loading && items.map((item, i) => {
|
|
72
|
+
if (item.kind === "separator") {
|
|
73
|
+
return _jsx("span", { className: "my-1 block border-t border-line" }, i);
|
|
74
|
+
}
|
|
75
|
+
if (item.kind === "label") {
|
|
76
|
+
return (_jsx("span", { className: "tamga-label block px-4 py-2", children: item.label }, i));
|
|
77
|
+
}
|
|
78
|
+
return (_jsxs("button", { role: item.checked === undefined ? "menuitem" : "menuitemradio", "aria-checked": item.checked, "data-selected": item.checked || undefined, "data-tamga-option": true, className: "tamga-option", disabled: item.disabled, style: {
|
|
79
|
+
color: item.state ? toneOf(item.state).fg : undefined,
|
|
80
|
+
opacity: item.disabled ? 0.45 : undefined,
|
|
81
|
+
cursor: item.disabled ? "not-allowed" : undefined,
|
|
82
|
+
}, onClick: () => {
|
|
83
|
+
item.onSelect?.();
|
|
84
|
+
setOpen(false);
|
|
85
|
+
}, children: [item.icon, item.label] }, i));
|
|
86
|
+
})] }))] }));
|
|
87
|
+
}
|
|
88
|
+
/* --------------------------- popover --------------------------- */
|
|
89
|
+
export function Popover({ trigger, title, children, footer, width = 288, align = "start", loading = false, loadingBlock, closeLabel, }) {
|
|
90
|
+
const [open, setOpen] = useState(false);
|
|
91
|
+
const box = useDismiss(open, () => setOpen(false));
|
|
92
|
+
return (_jsxs("div", { ref: box, className: "relative inline-flex", children: [_jsx("span", { onClick: () => setOpen((o) => !o), children: trigger }), open && (_jsxs("div", { className: "tamga-overlay absolute top-[var(--overlay-below)] z-40", style: { width, [align === "end" ? "right" : "left"]: 0 }, "aria-busy": loading || undefined, children: [title ? (_jsxs("div", { className: "tamga-head tamga-gutter tamga-section", children: [_jsx("h4", { className: "text-control font-semibold", children: title }), _jsx(MiniButton, { className: "ml-auto", "aria-label": closeLabel, onClick: () => setOpen(false), children: _jsx(Icon, { icon: Close, size: "xs" }) })] })) : null, _jsx("div", { className: "tamga-gutter py-4 text-small leading-relaxed", children: loading ? _jsx(SkeletonPanel, { lines: 3, block: loadingBlock }) : children }), footer ? (_jsx("div", { className: "tamga-gutter flex justify-end gap-2 border-t border-line py-3", children: footer })) : null] }))] }));
|
|
93
|
+
}
|
|
94
|
+
/* --------------------------- tooltip --------------------------- */
|
|
95
|
+
const placements = {
|
|
96
|
+
top: "bottom-[var(--overlay-above)] left-1/2 -translate-x-1/2",
|
|
97
|
+
bottom: "top-[var(--overlay-below)] left-1/2 -translate-x-1/2",
|
|
98
|
+
left: "right-[var(--overlay-below)] top-1/2 -translate-y-1/2",
|
|
99
|
+
right: "left-[var(--overlay-below)] top-1/2 -translate-y-1/2",
|
|
100
|
+
};
|
|
101
|
+
/* A hard triangle cut with clip-path — no rotated square, no border to line up.
|
|
102
|
+
Solid ink, square corners, same as the bubble it belongs to.
|
|
103
|
+
|
|
104
|
+
The BOX turns with the arrow. It used to be 12×6 for all four placements, so the sideways ones
|
|
105
|
+
were a 12-long spike where the vertical ones were a 12-wide wedge — the same clip-path in a box
|
|
106
|
+
that had not been rotated with it. Every arrow is now a 12 base and a 6 rise, whichever way it
|
|
107
|
+
points. */
|
|
108
|
+
const arrows = {
|
|
109
|
+
top: {
|
|
110
|
+
left: "50%", top: "100%", marginLeft: -6, width: 12, height: 6,
|
|
111
|
+
clipPath: "polygon(0 0, 100% 0, 50% 100%)",
|
|
112
|
+
},
|
|
113
|
+
bottom: {
|
|
114
|
+
left: "50%", bottom: "100%", marginLeft: -6, width: 12, height: 6,
|
|
115
|
+
clipPath: "polygon(50% 0, 0 100%, 100% 100%)",
|
|
116
|
+
},
|
|
117
|
+
left: {
|
|
118
|
+
top: "50%", left: "100%", marginTop: -6, width: 6, height: 12,
|
|
119
|
+
clipPath: "polygon(0 0, 100% 50%, 0 100%)",
|
|
120
|
+
},
|
|
121
|
+
right: {
|
|
122
|
+
top: "50%", right: "100%", marginTop: -6, width: 6, height: 12,
|
|
123
|
+
clipPath: "polygon(100% 0, 0 50%, 100% 100%)",
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* KABARCIK `fixed`, `absolute` DEĞİL.
|
|
128
|
+
*
|
|
129
|
+
* Ölçülen hata: dar bir kenar çubuğunda ipucu hiç görünmüyordu. Sebep
|
|
130
|
+
* `overflow`du — menü kaydırılabilir olduğu için (`overflow-y: auto`, ki yatayı
|
|
131
|
+
* da `auto` yapıyor) ipucu doğuyor, çiziliyor ve kabın dışında kaldığı için
|
|
132
|
+
* tamamen kırpılıyordu. Bu, kitin üç kez karşılaştığı aynı tuzağın üçüncü yüzü
|
|
133
|
+
* (kart, ray, şimdi ipucu).
|
|
134
|
+
*
|
|
135
|
+
* Z-index çözmüyor: hiçbir yığın sırası bir `overflow` kırpmasını aşamaz.
|
|
136
|
+
* Çözüm kabarcığı akıştan çıkarmak. `fixed` bir eleman en yakın kaydırma
|
|
137
|
+
* kabına değil GÖRÜNTÜ ALANINA göre yerleşiyor, yani hiçbir kap onu kesemiyor.
|
|
138
|
+
*
|
|
139
|
+
* KONUM HOVER'DA ÖLÇÜLÜYOR, çünkü `fixed` bir elemanın CSS ile tetikleyiciye
|
|
140
|
+
* hizalanmasının yolu yok. Ölçüm yalnız ipucu açılırken yapılıyor: kapalıyken
|
|
141
|
+
* hiçbir maliyeti yok.
|
|
142
|
+
*/
|
|
143
|
+
export function Tooltip({ label, placement = "bottom", children, }) {
|
|
144
|
+
const sarmal = useRef(null);
|
|
145
|
+
const [konum, setKonum] = useState(null);
|
|
146
|
+
const olc = useCallback(() => {
|
|
147
|
+
const el = sarmal.current;
|
|
148
|
+
if (!el)
|
|
149
|
+
return;
|
|
150
|
+
const r = el.getBoundingClientRect();
|
|
151
|
+
const bosluk = 8;
|
|
152
|
+
const yerler = {
|
|
153
|
+
top: { left: r.left + r.width / 2, top: r.top - bosluk },
|
|
154
|
+
bottom: { left: r.left + r.width / 2, top: r.bottom + bosluk },
|
|
155
|
+
left: { left: r.left - bosluk, top: r.top + r.height / 2 },
|
|
156
|
+
right: { left: r.right + bosluk, top: r.top + r.height / 2 },
|
|
157
|
+
};
|
|
158
|
+
setKonum(yerler[placement]);
|
|
159
|
+
}, [placement]);
|
|
160
|
+
const kapat = useCallback(() => setKonum(null), []);
|
|
161
|
+
/* Kaydırma ya da yeniden boyutlanma ipucunun altından tetikleyiciyi çekiyor;
|
|
162
|
+
o an kapanması, yanlış yerde durmasından iyi. */
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!konum)
|
|
165
|
+
return;
|
|
166
|
+
window.addEventListener("scroll", kapat, true);
|
|
167
|
+
window.addEventListener("resize", kapat);
|
|
168
|
+
return () => {
|
|
169
|
+
window.removeEventListener("scroll", kapat, true);
|
|
170
|
+
window.removeEventListener("resize", kapat);
|
|
171
|
+
};
|
|
172
|
+
}, [konum, kapat]);
|
|
173
|
+
const kaydirma = {
|
|
174
|
+
top: "translate(-50%, -100%)",
|
|
175
|
+
bottom: "translate(-50%, 0)",
|
|
176
|
+
left: "translate(-100%, -50%)",
|
|
177
|
+
right: "translate(0, -50%)",
|
|
178
|
+
};
|
|
179
|
+
return (_jsxs("span", { ref: sarmal, className: "relative inline-flex", onPointerEnter: olc, onPointerLeave: kapat, onFocusCapture: olc, onBlurCapture: kapat, children: [children, konum && (_jsxs("span", { role: "tooltip", className: "pointer-events-none fixed z-40 whitespace-nowrap px-2 py-1 text-small", style: {
|
|
180
|
+
left: konum.left,
|
|
181
|
+
top: konum.top,
|
|
182
|
+
transform: kaydirma[placement],
|
|
183
|
+
background: "var(--color-ink)",
|
|
184
|
+
color: "var(--color-page)",
|
|
185
|
+
borderRadius: "var(--radius-ctl)",
|
|
186
|
+
}, children: [label, _jsx("span", { "aria-hidden": true, className: "absolute block", style: { background: "var(--color-ink)", ...arrows[placement] } })] }))] }));
|
|
187
|
+
}
|
|
188
|
+
export function Toast({ tone = "neutral", title, description, action, onDismiss, dismissLabel, }) {
|
|
189
|
+
const mark = tone === "neutral" ? "var(--color-ink)" : toneOf(tone).mark;
|
|
190
|
+
return (_jsxs("div", { role: "status", className: "tamga-toast tamga-overlay flex w-80 items-start gap-4 p-4", style: { animation: "tamga-toast-in var(--duration-base) var(--ease-standard) both" }, children: [_jsx("span", { className: "mt-1 size-2.5 shrink-0", style: { background: mark } }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-body font-medium text-ink", children: title }), description ? (_jsx("p", { className: "mt-1 text-small leading-relaxed text-ink-faint", children: description })) : null, action ? _jsx("div", { className: "mt-3", children: action }) : null] }), onDismiss ? (_jsx(MiniButton, { onClick: onDismiss, "aria-label": dismissLabel, children: _jsx(Icon, { icon: Close, size: "xs" }) })) : null] }));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Where toasts land. Top-right is the default: it is the one corner that
|
|
194
|
+
* never covers a table's actions column or a form's footer buttons.
|
|
195
|
+
*/
|
|
196
|
+
const corners = {
|
|
197
|
+
"top-right": "top-6 right-6 items-end",
|
|
198
|
+
"top-left": "top-6 left-6 items-start",
|
|
199
|
+
"bottom-right": "bottom-6 right-6 items-end",
|
|
200
|
+
"bottom-left": "bottom-6 left-6 items-start",
|
|
201
|
+
};
|
|
202
|
+
export function ToastViewport({ position = "top-right", children, }) {
|
|
203
|
+
return (_jsx("div", { className: `pointer-events-none fixed z-50 flex flex-col gap-2 ${corners[position]}`, children: _jsx("div", { className: "pointer-events-auto flex flex-col gap-2", children: children }) }));
|
|
204
|
+
}
|
|
205
|
+
/* ---------------------------- dialog --------------------------- */
|
|
206
|
+
/**
|
|
207
|
+
* The modal shell. Hooks cannot run behind an early return, so the trap lives
|
|
208
|
+
* here and the surface below stays a plain component.
|
|
209
|
+
*/
|
|
210
|
+
/**
|
|
211
|
+
* Diyalog genişliği. Bir VARYANT, ayrı bir bileşen değil (kitin kuralı: adı
|
|
212
|
+
* bölünen şey bileşen, ayarlanan şey prop).
|
|
213
|
+
*
|
|
214
|
+
* `wide` bir ÖNİZLEME için var: bir bloğu ya da tam bir ekran şablonunu 448
|
|
215
|
+
* pikselde göstermek, gösterdiğini gizlemek olur. `md` hâlâ varsayılan, çünkü
|
|
216
|
+
* bir diyaloğun asıl işi bir KARAR sormak ve geniş bir karar kutusu, kararı
|
|
217
|
+
* daha kolay yapmıyor.
|
|
218
|
+
*
|
|
219
|
+
* `full` BİR ÇALIŞMA YÜZEYİ için: bir görsel düzenleyici, bir tuval, bir harita
|
|
220
|
+
* seçici. Bunlar bir karar kutusu değil bir EKRAN, ve `wide` bile onlara az
|
|
221
|
+
* geliyor — kalan kenar boşluğu tuvalden çalınan alan. Bu boyutta panelin
|
|
222
|
+
* kenarı, yarıçapı ve kaydırma gölgesi de kalkıyor: yükseltilecek bir zemin
|
|
223
|
+
* kalmadığında yükselme işareti de anlamsız. Nadir olması gerekiyor; bir formu
|
|
224
|
+
* tam ekran açmak, formu daha kolay doldurmuyor.
|
|
225
|
+
*/
|
|
226
|
+
const DIALOG_GENISLIK = {
|
|
227
|
+
md: "max-w-md",
|
|
228
|
+
lg: "max-w-3xl",
|
|
229
|
+
wide: "max-w-(--dialog-wide)",
|
|
230
|
+
full: "max-w-none",
|
|
231
|
+
};
|
|
232
|
+
function DialogShell({ open, onClose, loading, label, size = "md", children, }) {
|
|
233
|
+
const panel = useFocusTrap(open);
|
|
234
|
+
/* Aynı kaçak `Dialog`da da vardı: odak hapsediliyor ama arkadaki sayfa
|
|
235
|
+
kayıyordu. Kullanıcı etkileşemediği bir şeyi kaydırabiliyordu. */
|
|
236
|
+
useScrollLock(open);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (!open)
|
|
239
|
+
return;
|
|
240
|
+
const esc = (e) => e.key === "Escape" && onClose();
|
|
241
|
+
document.addEventListener("keydown", esc);
|
|
242
|
+
return () => document.removeEventListener("keydown", esc);
|
|
243
|
+
}, [open, onClose]);
|
|
244
|
+
if (!open)
|
|
245
|
+
return null;
|
|
246
|
+
/* Tam ekranda dış kenar boşluğu YOK: 24 piksel, bir tuvalden çalınan 24
|
|
247
|
+
pikseldir. Panel de yüzeyi kaplıyor ve dikey bir akış oluyor, ki gövde
|
|
248
|
+
kendi içinde kaysın ve başlık ile ayak yerinde kalsın. */
|
|
249
|
+
const tam = size === "full";
|
|
250
|
+
return (_jsxs("div", { className: cn("fixed inset-0 z-50 flex items-center justify-center", tam ? "p-0" : "p-6"), role: "dialog", "aria-modal": true, "aria-label": label, children: [_jsx("div", { className: "tamga-scrim absolute inset-0", "aria-hidden": true, onClick: onClose }), _jsx("div", { ref: panel, className: cn("tamga-overlay relative z-10 w-full", tam && "tamga-overlay-full", DIALOG_GENISLIK[size]), "aria-busy": loading || undefined, children: children })] }));
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Yıkıcı bir eylemin önündeki kapı.
|
|
254
|
+
*
|
|
255
|
+
* NEDEN AYRI BİR BİLEŞEN. `Dialog` zaten var ve bu onun üstüne kurulu; ama
|
|
256
|
+
* "emin misin" diyaloğu her seferinde elle kurulduğunda üç şey kayıyor ve
|
|
257
|
+
* üçü de güvenlikle ilgili:
|
|
258
|
+
*
|
|
259
|
+
* 1. ONAY DÜĞMESİ FİİLİ TAŞIR, "Tamam"ı değil. Bir kullanıcı diyaloğun
|
|
260
|
+
* metnini okumadan düğmeye basar; okuduğu tek şey düğmenin üstündeki
|
|
261
|
+
* kelimedir. "Tamam" hiçbir şey söylemez, "Sil" söyler.
|
|
262
|
+
* 2. ODAK VAZGEÇ'TE AÇILIR. Yıkıcı bir diyalogda odağın onay düğmesinde
|
|
263
|
+
* olması, Enter'a basan birinin kaydı silmesi demek. Güvenli olan
|
|
264
|
+
* varsayılan olmalı.
|
|
265
|
+
* 3. GERİ ALINAMAZLIK YAZILIR. Gövde metni ne olacağını değil NEYİN GERİ
|
|
266
|
+
* GELMEYECEĞİNİ söyler; kullanıcının kararı buna bağlı.
|
|
267
|
+
*
|
|
268
|
+
* KELİMELER ÇAĞIRANIN. Kit hiçbir dil bilmiyor: başlık, gövde ve iki düğmenin
|
|
269
|
+
* etiketi dışarıdan geliyor.
|
|
270
|
+
*/
|
|
271
|
+
export function ConfirmDialog({ open, onClose, onConfirm, title, children, confirmLabel, cancelLabel, closeLabel, tone = "danger", busy = false, confirmDisabled = false, }) {
|
|
272
|
+
return (_jsx(Dialog, { open: open, onClose: onClose, title: title, closeLabel: closeLabel, footer: _jsxs(_Fragment, { children: [_jsx(Button, { type: "button", onClick: onClose, disabled: busy, "data-autofocus": true, children: cancelLabel }), _jsx(Button, { variant: tone, onClick: onConfirm, disabled: busy || confirmDisabled, children: confirmLabel })] }), children: children }));
|
|
273
|
+
}
|
|
274
|
+
export function Dialog({ open, onClose, title, children, footer, loading = false, closeLabel, size = "md", }) {
|
|
275
|
+
return (_jsxs(DialogShell, { open: open, onClose: onClose, loading: loading, label: title, size: size, children: [_jsxs("div", { className: "tamga-head tamga-gutter tamga-section", children: [_jsx("h3", { className: "text-subhead font-semibold", children: title }), _jsx(MiniButton, { onClick: onClose, "aria-label": closeLabel, className: "ml-auto", children: _jsx(Icon, { icon: Close, size: "xs" }) })] }), _jsx("div", { className: cn("tamga-gutter py-6 text-body leading-relaxed", size === "full" && "min-h-0 flex-1 overflow-y-auto"), children: loading ? _jsx(SkeletonPanel, { lines: 4 }) : children }), footer ? (_jsx("div", { className: "tamga-gutter flex justify-end gap-2 border-t border-line py-4", children: footer })) : null] }));
|
|
276
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination.
|
|
3
|
+
*
|
|
4
|
+
* Gerekçe: docs/gerekce/02-veri-ve-liste.md
|
|
5
|
+
*/
|
|
6
|
+
/** Gösterilecek numaraları hesaplar. `null` = kısaltma. */
|
|
7
|
+
export declare function pageWindow(page: number, pages: number, span?: number): (number | null)[];
|
|
8
|
+
export declare function Pagination({ page, pageSize, total, onChange, labels, span, className, }: {
|
|
9
|
+
/** Starts at 1. TR: 1'den başlar. */
|
|
10
|
+
page: number;
|
|
11
|
+
pageSize: number;
|
|
12
|
+
/**
|
|
13
|
+
* Total records. Unknown, the numbers are hidden. TR: Toplam kayıt. Bilinmiyorsa numaralar
|
|
14
|
+
* gizlenir.
|
|
15
|
+
*/
|
|
16
|
+
total?: number;
|
|
17
|
+
onChange?: (next: number) => void;
|
|
18
|
+
/**
|
|
19
|
+
* How many numbers show on each side of the active page. The window is `2·span + 5`: seven
|
|
20
|
+
* numbers at the default 1, nine at 2. TR: Aktif sayfanın her iki yanında kaç numara
|
|
21
|
+
* görünsün. Pencere genişliği `2·span + 5`: varsayılan 1 ile yedi, 2 ile dokuz numara.
|
|
22
|
+
*/
|
|
23
|
+
span?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Every visible string. The kit pulls no translations. `summary` is a function: languages
|
|
26
|
+
* place numbers differently ("1-25 / 240" · "1–25 of 240"), so the caller builds the
|
|
27
|
+
* template. TR: Görünen her metin. Kit çeviri çekmez. `summary` bir fonksiyon: dillerin sayı
|
|
28
|
+
* yerleşimi farklı ("1-25 / 240" · "1–25 of 240"), şablonu çağıran kurar.
|
|
29
|
+
*/
|
|
30
|
+
labels: {
|
|
31
|
+
previous: string;
|
|
32
|
+
next: string;
|
|
33
|
+
page: (n: number) => string;
|
|
34
|
+
summary?: (from: number, to: number, total: number) => string;
|
|
35
|
+
};
|
|
36
|
+
className?: string;
|
|
37
|
+
}): import("react").JSX.Element;
|
|
38
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/components/pagination.tsx"],"names":[],"mappings":"AAMA;;;;GAIG;AAEH,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAiBnF;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,MAAM,EACN,IAAQ,EACR,SAAS,GACV,EAAE;IACD,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;QAC5B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;KAC/D,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA8DA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Icon } from "./icon.js";
|
|
5
|
+
import { CaretLeft, CaretRight } from "./icons.js";
|
|
6
|
+
/**
|
|
7
|
+
* Pagination.
|
|
8
|
+
*
|
|
9
|
+
* Gerekçe: docs/gerekce/02-veri-ve-liste.md
|
|
10
|
+
*/
|
|
11
|
+
/** Gösterilecek numaraları hesaplar. `null` = kısaltma. */
|
|
12
|
+
export function pageWindow(page, pages, span = 1) {
|
|
13
|
+
/* Kısaltmasız sığan sayfa adedi `span`den TÜRETİLİYOR, sabit 7 değil:
|
|
14
|
+
pencere ilk + [kısaltma] + (2·span+1) + [kısaltma] + son, yani 2·span+5.
|
|
15
|
+
Sabit 7 yazılıydı ve `span` büyütüldüğünde sekiz sayfalık bir liste
|
|
16
|
+
gereksiz yere kısaltılıyordu. */
|
|
17
|
+
const sigan = span * 2 + 5;
|
|
18
|
+
if (pages <= sigan)
|
|
19
|
+
return Array.from({ length: pages }, (_, i) => i + 1);
|
|
20
|
+
const out = [1];
|
|
21
|
+
const from = Math.max(2, page - span);
|
|
22
|
+
const to = Math.min(pages - 1, page + span);
|
|
23
|
+
if (from > 2)
|
|
24
|
+
out.push(null);
|
|
25
|
+
for (let i = from; i <= to; i++)
|
|
26
|
+
out.push(i);
|
|
27
|
+
if (to < pages - 1)
|
|
28
|
+
out.push(null);
|
|
29
|
+
out.push(pages);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
export function Pagination({ page, pageSize, total, onChange, labels, span = 1, className, }) {
|
|
33
|
+
const pages = total !== undefined ? Math.max(1, Math.ceil(total / pageSize)) : undefined;
|
|
34
|
+
const canPrev = page > 1;
|
|
35
|
+
const canNext = pages === undefined ? true : page < pages;
|
|
36
|
+
const from = (page - 1) * pageSize + 1;
|
|
37
|
+
const to = total !== undefined ? Math.min(page * pageSize, total) : page * pageSize;
|
|
38
|
+
return (_jsxs("nav", { className: cn("flex flex-wrap items-center gap-3", className), "aria-label": labels.page(page), children: [total !== undefined && labels.summary ? (_jsx("span", { className: "tamga-label tabular-nums", children: labels.summary(from, to, total) })) : null, _jsxs("span", { className: "ml-auto flex items-center gap-1.5", children: [_jsx("button", { type: "button", className: "tamga-icon-btn shrink-0", "aria-label": labels.previous, disabled: !canPrev, onClick: () => canPrev && onChange?.(page - 1), children: _jsx(Icon, { icon: CaretLeft, size: "sm" }) }), pages !== undefined
|
|
39
|
+
? pageWindow(page, pages, span).map((n, i) => n === null ? (_jsx("span", { className: "px-1 text-small text-ink-faint", "aria-hidden": true, children: "\u2026" }, `gap-${i}`)) : (_jsx("button", { type: "button",
|
|
40
|
+
/* Seçili sayfa DOLGU almaz: "buradasın" bir eylem değildir
|
|
41
|
+
(Yasa 2). Gerekçesi `.tamga-page`'in üstünde — bu satır
|
|
42
|
+
bir zamanlar `.tamga-option` diyordu ve yorumun tersini
|
|
43
|
+
yapıyordu. */
|
|
44
|
+
className: "tamga-page tabular-nums", "aria-current": n === page ? "page" : undefined, "aria-label": labels.page(n), onClick: () => onChange?.(n), children: n }, n)))
|
|
45
|
+
: null, _jsx("button", { type: "button", className: "tamga-icon-btn shrink-0", "aria-label": labels.next, disabled: !canNext, onClick: () => canNext && onChange?.(page + 1), children: _jsx(Icon, { icon: CaretRight, size: "sm" }) })] })] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pasta / halka grafik — PARÇA-BÜTÜN, ve yalnız o.
|
|
3
|
+
*
|
|
4
|
+
* Gerekçe: docs/gerekce/03-grafik-ve-olcum.md
|
|
5
|
+
*/
|
|
6
|
+
export type PieSlice = {
|
|
7
|
+
label: string;
|
|
8
|
+
value: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function PieChart({ slices, total, totalLabel, formatValue, inner, size, className, }: {
|
|
11
|
+
/**
|
|
12
|
+
* Five slices at most: more does not read, and a chart that does not read is wrong
|
|
13
|
+
* information. TR: En çok beş dilim: fazlası okunmuyor, ve okunmayan bir grafik yanlış
|
|
14
|
+
* bilgidir.
|
|
15
|
+
*/
|
|
16
|
+
slices: readonly PieSlice[];
|
|
17
|
+
/**
|
|
18
|
+
* The denominator. Left out, the sum of the slices; but it MUST be givable, because the
|
|
19
|
+
* slices do not always cover the whole: if a query leaves out customers with no gender, the
|
|
20
|
+
* percentages are percentages of that smaller set, and only the caller knows it. TR: Payda.
|
|
21
|
+
* Verilmezse dilimlerin toplamı; ama VERİLEBİLMESİ şart, çünkü dilimler her zaman bütünü
|
|
22
|
+
* kapsamıyor: bir sorgu cinsiyeti boş olan müşterileri dışarıda bırakıyorsa yüzdeler o eksik
|
|
23
|
+
* kümenin yüzdesidir ve bunu ancak çağıran bilir.
|
|
24
|
+
*/
|
|
25
|
+
total?: number;
|
|
26
|
+
/**
|
|
27
|
+
* The name of the total in the middle of the ring: "members", "orders". TR: Halkanın
|
|
28
|
+
* ortasındaki toplamın adı: "üye", "sipariş".
|
|
29
|
+
*/
|
|
30
|
+
totalLabel?: string;
|
|
31
|
+
formatValue?: (v: number) => string;
|
|
32
|
+
/**
|
|
33
|
+
* 0 a full pie, between 0 and 1 the ring's thickness. TR: 0 tam pasta, 0 ile 1 arası halka
|
|
34
|
+
* kalınlığı.
|
|
35
|
+
*/
|
|
36
|
+
inner?: number;
|
|
37
|
+
size?: number;
|
|
38
|
+
className?: string;
|
|
39
|
+
}): import("react").JSX.Element;
|
|
40
|
+
//# sourceMappingURL=pie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pie.d.ts","sourceRoot":"","sources":["../../src/components/pie.tsx"],"names":[],"mappings":"AAKA;;;;GAIG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAUF,wBAAgB,QAAQ,CAAC,EACvB,MAAM,EACN,KAAK,EACL,UAAU,EACV,WAAoB,EACpB,KAAY,EACZ,IAAU,EACV,SAAS,GACV,EAAE;IACD;;;;OAIG;IACH,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC5B;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAwJA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
const R = 100;
|
|
6
|
+
/** Kutup koordinatından kartezyene; 0° saat 12 yönü. */
|
|
7
|
+
function nokta(aci, yaricap) {
|
|
8
|
+
const rad = ((aci - 90) * Math.PI) / 180;
|
|
9
|
+
return [R + yaricap * Math.cos(rad), R + yaricap * Math.sin(rad)];
|
|
10
|
+
}
|
|
11
|
+
export function PieChart({ slices, total, totalLabel, formatValue = String, inner = 0.58, size = 200, className, }) {
|
|
12
|
+
const id = useId();
|
|
13
|
+
/* Üzerinde durulan dilimin indeksi. Grafik ile legend AYNI durumu paylaşıyor:
|
|
14
|
+
legend satırına gelmek dilimi, dilime gelmek legend satırını vurguluyor.
|
|
15
|
+
İkisi ayrı olsaydı kullanıcı hangi satırın hangi dilim olduğunu yine gözle
|
|
16
|
+
eşleştirmek zorunda kalırdı, ki bu tam da legend'ın çözdüğü sorun. */
|
|
17
|
+
const [uzerinde, setUzerinde] = useState(null);
|
|
18
|
+
const toplam = total ?? slices.reduce((n, s) => n + s.value, 0);
|
|
19
|
+
const ic = R * inner;
|
|
20
|
+
let aci = 0;
|
|
21
|
+
const parcalar = slices.map((s, i) => {
|
|
22
|
+
const pay = toplam > 0 ? s.value / toplam : 0;
|
|
23
|
+
const bas = aci;
|
|
24
|
+
const son = aci + pay * 360;
|
|
25
|
+
aci = son;
|
|
26
|
+
return { ...s, pay, bas, son, renk: `var(--color-chart-${(i % 5) + 1})` };
|
|
27
|
+
});
|
|
28
|
+
return (_jsxs("div", { className: cn("flex flex-wrap items-center gap-6", className), children: [_jsxs("svg", {
|
|
29
|
+
/* `viewBox` dilimin dışarı taşan halini de kapsıyor: üzerine gelinen
|
|
30
|
+
dilim 6 birim dışarı kayıyor, ve kutu büyümezse kırpılıyor. */
|
|
31
|
+
viewBox: `-8 -8 ${R * 2 + 16} ${R * 2 + 16}`, width: size, height: size, className: "shrink-0 overflow-visible", role: "img", "aria-labelledby": id, onMouseLeave: () => setUzerinde(null), children: [_jsx("title", { id: id, children: parcalar
|
|
32
|
+
.map((p) => `${p.label}: ${formatValue(p.value)} (%${(p.pay * 100).toFixed(1)})`)
|
|
33
|
+
.join(", ") }), parcalar.map((p, i) => {
|
|
34
|
+
if (p.pay <= 0)
|
|
35
|
+
return null;
|
|
36
|
+
/* TEK DİLİM %100 İSE YAY ÇİZİLEMEZ: başlangıç ve bitiş noktası
|
|
37
|
+
çakışır ve `A` komutu hiçbir şey çizmez. Tam daire ayrı bir yol. */
|
|
38
|
+
if (p.pay >= 0.999) {
|
|
39
|
+
return (_jsxs("g", { children: [_jsx("circle", { cx: R, cy: R, r: R, fill: p.renk }), ic > 0 && _jsx("circle", { cx: R, cy: R, r: ic, fill: "var(--color-shell)" })] }, p.label));
|
|
40
|
+
}
|
|
41
|
+
const [x1, y1] = nokta(p.bas, R);
|
|
42
|
+
const [x2, y2] = nokta(p.son, R);
|
|
43
|
+
const [x3, y3] = nokta(p.son, ic);
|
|
44
|
+
const [x4, y4] = nokta(p.bas, ic);
|
|
45
|
+
const buyuk = p.son - p.bas > 180 ? 1 : 0;
|
|
46
|
+
const d = ic > 0
|
|
47
|
+
? `M ${x1} ${y1} A ${R} ${R} 0 ${buyuk} 1 ${x2} ${y2} L ${x3} ${y3} A ${ic} ${ic} 0 ${buyuk} 0 ${x4} ${y4} Z`
|
|
48
|
+
: `M ${R} ${R} L ${x1} ${y1} A ${R} ${R} 0 ${buyuk} 1 ${x2} ${y2} Z`;
|
|
49
|
+
/* ÜZERİNE GELİNEN DİLİM DIŞARI KAYIYOR, büyümüyor.
|
|
50
|
+
Büyütmek dilimin AÇISINI değiştirmiyor ama gözde öyle bir izlenim
|
|
51
|
+
bırakıyor: bir oran grafiğinde en son isteyeceğin şey. Merkezden
|
|
52
|
+
dışarı kaydırmak açıyı da yarıçapı da koruyor. */
|
|
53
|
+
const orta = (p.bas + p.son) / 2;
|
|
54
|
+
const [kx, ky] = nokta(orta, 6);
|
|
55
|
+
const secili = uzerinde === i;
|
|
56
|
+
return (_jsx("path", { d: d, fill: p.renk,
|
|
57
|
+
/* Dilimler arasındaki ince ayraç zemin renginde: iki komşu
|
|
58
|
+
dilim aynı tonda olsa bile sınır görünüyor. */
|
|
59
|
+
stroke: "var(--color-shell)", strokeWidth: 2, style: {
|
|
60
|
+
transform: secili ? `translate(${kx - R}px, ${ky - R}px)` : undefined,
|
|
61
|
+
/* Öteki dilimler soluyor: vurgulanan şey parlamıyor, çevresi
|
|
62
|
+
çekiliyor. Rengi açmak paleti bozardı. */
|
|
63
|
+
opacity: uzerinde === null || secili ? 1 : 0.45,
|
|
64
|
+
transition: "opacity var(--duration-press) var(--ease-standard), transform var(--duration-press) var(--ease-standard)",
|
|
65
|
+
}, onMouseEnter: () => setUzerinde(i) }, p.label));
|
|
66
|
+
}), ic > 0 && toplam > 0 && (_jsxs(_Fragment, { children: [_jsx("text", { x: R, y: R - 4, textAnchor: "middle", className: "fill-[var(--color-ink)] font-semibold", style: { fontSize: 26 }, children: uzerinde === null
|
|
67
|
+
? formatValue(toplam)
|
|
68
|
+
: formatValue(parcalar[uzerinde]?.value ?? 0) }), _jsx("text", { x: R, y: R + 20, textAnchor: "middle", className: "fill-[var(--color-ink-faint)]", style: { fontSize: 14 }, children: uzerinde === null
|
|
69
|
+
? (totalLabel ?? "")
|
|
70
|
+
: `${parcalar[uzerinde]?.label} · %${((parcalar[uzerinde]?.pay ?? 0) * 100).toFixed(1)}` })] }))] }), _jsx("dl", { className: "min-w-0 flex-1 flex-col gap-2", children: parcalar.map((p, i) => (_jsxs("div", { className: "flex cursor-default items-baseline gap-2.5 rounded-(--radius-mark) px-1.5 py-1 transition-[background] duration-(--duration-press)", style: {
|
|
71
|
+
background: uzerinde === i ? "var(--color-hover)" : undefined,
|
|
72
|
+
opacity: uzerinde === null || uzerinde === i ? 1 : 0.5,
|
|
73
|
+
}, onMouseEnter: () => setUzerinde(i), onMouseLeave: () => setUzerinde(null), children: [_jsx("span", { "aria-hidden": true, className: "size-2.5 shrink-0 translate-y-px rounded-(--radius-mark)", style: { background: p.renk } }), _jsx("dt", { className: "min-w-0 flex-1 truncate text-ink", children: p.label }), _jsxs("dd", { className: "shrink-0 tabular-nums text-ink", children: [formatValue(p.value), _jsxs("span", { className: "ml-1.5 text-ink-faint", children: ["%", (p.pay * 100).toFixed(1)] })] })] }, p.label))) })] }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Tone } from "./tone.js";
|
|
2
|
+
export { Spinner } from "./spinner.js";
|
|
3
|
+
export declare function Separator({ vertical }: {
|
|
4
|
+
vertical?: boolean;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
/** Running is not a deviation, so the live mark stays neutral — the pulse carries it. */
|
|
7
|
+
export declare function Beacon({ live, label, severity, }: {
|
|
8
|
+
live?: boolean;
|
|
9
|
+
label?: string;
|
|
10
|
+
/**
|
|
11
|
+
* a bare beacon outranks nothing in particular; give it a rank to compete TR: çıplak bir
|
|
12
|
+
* işaret hiçbir şeye üstün gelmez; yarışması için ona bir sıra verin
|
|
13
|
+
*/
|
|
14
|
+
severity?: number | null;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
export declare function Alert({ state, title, children, action, }: {
|
|
17
|
+
state?: Tone;
|
|
18
|
+
title: string;
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
action?: React.ReactNode;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
export declare function Breadcrumb({ items, label, }: {
|
|
23
|
+
items: {
|
|
24
|
+
label: string;
|
|
25
|
+
href?: string;
|
|
26
|
+
}[];
|
|
27
|
+
/**
|
|
28
|
+
* The landmark's accessible name; see the note on Spinner. TR: Landmark'ın erişilebilir adı;
|
|
29
|
+
* Spinner'daki nota bakın.
|
|
30
|
+
*/
|
|
31
|
+
label: string;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
export declare function Field({ label, description, error, children, htmlFor, }: {
|
|
34
|
+
label: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
children: React.ReactNode;
|
|
38
|
+
htmlFor?: string;
|
|
39
|
+
}): import("react").JSX.Element;
|
|
40
|
+
/** A plain select: the same overlay as the searchable one, without the search. */
|
|
41
|
+
export declare function Select({ options, value, onChange, placeholder, loading, loadingRows, className, }: {
|
|
42
|
+
options: string[];
|
|
43
|
+
value?: string;
|
|
44
|
+
onChange?: (v: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Visible when nothing is chosen; user-facing, so the caller supplies it translated. TR:
|
|
47
|
+
* Hiçbir şey seçilmemişken görünür; kullanıcıya gösterildiği için çevirisini çağıran veriyor.
|
|
48
|
+
*/
|
|
49
|
+
placeholder: string;
|
|
50
|
+
/**
|
|
51
|
+
* options not in yet; the list holds its shape instead of showing a spinner TR: seçenekler
|
|
52
|
+
* henüz gelmedi; liste bir dönen simge göstermek yerine şeklini koruyor
|
|
53
|
+
*/
|
|
54
|
+
loading?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* How many skeleton rows to hold while `loading`. SETTLED 2026-08-19 (open question 4): this
|
|
57
|
+
* is never a taste value; it is the number of records that are actually coming, because the
|
|
58
|
+
* whole point of the skeleton is that the panel does not resize when they land. Where that
|
|
59
|
+
* number comes from (api-kontrati.html A7, KİLİTLİ): · paginated list → the request's
|
|
60
|
+
* `page_size` (DRF default 25, max 100) · last page → `meta.count - (page - 1) * page_size` ·
|
|
61
|
+
* cursor list → `page_size` (no `count` is returned, by design) · known-size menu → the item
|
|
62
|
+
* count the caller already knows The default of 4 fits a menu, not a list. A list that leaves
|
|
63
|
+
* it at 4 is a bug that looks like a design choice. TR: `loading` sürerken kaç iskelet satırı
|
|
64
|
+
* tutulacağı. 2026-08-19'da KARARA BAĞLANDI (açık soru 4): bu asla bir zevk değeri değil;
|
|
65
|
+
* gerçekten gelmekte olan kayıt sayısı, çünkü iskeletin bütün amacı kayıtlar geldiğinde
|
|
66
|
+
* panelin yeniden boyutlanmaması. O sayının nereden geldiği (api-kontrati.html A7, KİLİTLİ):
|
|
67
|
+
* · sayfalı liste → isteğin `page_size` değeri (DRF varsayılanı 25, en çok 100) · son sayfa →
|
|
68
|
+
* `meta.count - (page - 1) * page_size` · imleçli liste → `page_size` (tasarım gereği `count`
|
|
69
|
+
* dönmüyor) · boyu bilinen menü → çağıranın zaten bildiği öğe sayısı Varsayılan 4 bir menüye
|
|
70
|
+
* uyuyor, bir listeye değil. 4'te bırakılmış bir liste, tasarım kararı gibi görünen bir
|
|
71
|
+
* hatadır.
|
|
72
|
+
*/
|
|
73
|
+
loadingRows?: number;
|
|
74
|
+
/** Genişlik ve konum çağıranın. Varsayılan `w-56`; `w-full` verildiğinde
|
|
75
|
+
* tailwind-merge onu ezer, yani kontrol kabına uyar. */
|
|
76
|
+
className?: string;
|
|
77
|
+
}): import("react").JSX.Element;
|
|
78
|
+
/** Side panel. Overlay plane, so it carries the 6px offset and a scrim. */
|
|
79
|
+
export declare function Sheet({ open, onClose, title, children, footer, loading, closeLabel, }: {
|
|
80
|
+
open: boolean;
|
|
81
|
+
onClose: () => void;
|
|
82
|
+
title: string;
|
|
83
|
+
children: React.ReactNode;
|
|
84
|
+
footer?: React.ReactNode;
|
|
85
|
+
/**
|
|
86
|
+
* Accessible name for the close control, supplied by the caller (docs/08 rule 5). TR: Kapatma
|
|
87
|
+
* kontrolünün erişilebilir adı, çağıran veriyor (docs/08 kural 5).
|
|
88
|
+
*/
|
|
89
|
+
closeLabel: string;
|
|
90
|
+
/**
|
|
91
|
+
* contents not in yet; the panel holds its shape instead of showing a spinner TR: içerik
|
|
92
|
+
* henüz gelmedi; panel bir dönen simge göstermek yerine şeklini koruyor
|
|
93
|
+
*/
|
|
94
|
+
loading?: boolean;
|
|
95
|
+
}): import("react").JSX.Element | null;
|
|
96
|
+
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/components/primitives.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAWtC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAQvC,wBAAgB,SAAS,CAAC,EAAE,QAAgB,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,+BAErE;AAED,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,EACrB,IAAW,EACX,KAAK,EACL,QAAa,GACd,EAAE;IACD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,+BAQA;AAED,wBAAgB,KAAK,CAAC,EACpB,KAAiB,EACjB,KAAK,EACL,QAAQ,EACR,MAAM,GACP,EAAE;IACD,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B,+BAoBA;AAED,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,GACN,EAAE;IACD,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACf,+BA0BA;AAED,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,OAAO,GACR,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,+BAgBA;AAED,kFAAkF;AAClF,wBAAgB,MAAM,CAAC,EACrB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAAW,EACX,OAAe,EACf,WAAe,EACf,SAAS,GACV,EAAE;IACD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;6DACyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAyEA;AAED,2EAA2E;AAC3E,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAe,EACf,UAAU,GACX,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,sCA2CA"}
|