tamga-ui 0.6.0 → 0.7.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/CHANGELOG.md +133 -0
- package/dist/blocks/danger-zone.d.ts +54 -0
- package/dist/blocks/danger-zone.d.ts.map +1 -0
- package/dist/blocks/danger-zone.js +18 -0
- package/dist/blocks/index.d.ts +1 -0
- package/dist/blocks/index.d.ts.map +1 -1
- package/dist/blocks/index.js +1 -0
- package/dist/components/appearance.d.ts +91 -5
- package/dist/components/appearance.d.ts.map +1 -1
- package/dist/components/appearance.js +164 -18
- package/dist/components/button.d.ts +2 -1
- package/dist/components/button.d.ts.map +1 -1
- package/dist/components/button.js +1 -1
- package/dist/components/chrome.d.ts +10 -1
- package/dist/components/chrome.d.ts.map +1 -1
- package/dist/components/chrome.js +2 -2
- package/dist/components/date-picker.d.ts +142 -4
- package/dist/components/date-picker.d.ts.map +1 -1
- package/dist/components/date-picker.js +331 -40
- package/dist/components/disclosure.d.ts +37 -2
- package/dist/components/disclosure.d.ts.map +1 -1
- package/dist/components/disclosure.js +13 -3
- package/dist/components/display.d.ts +35 -6
- package/dist/components/display.d.ts.map +1 -1
- package/dist/components/display.js +21 -7
- package/dist/components/input.d.ts +2 -1
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/primitives.d.ts +3 -1
- package/dist/components/primitives.d.ts.map +1 -1
- package/dist/components/primitives.js +2 -2
- package/dist/fonts.css +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/kit.css +115 -3
- package/dist/lib/image.d.ts +2 -0
- package/dist/lib/image.d.ts.map +1 -1
- package/dist/lib/image.js +1 -1
- package/dist/lib/uyar.d.ts +2 -0
- package/dist/lib/uyar.d.ts.map +1 -0
- package/dist/lib/uyar.js +10 -0
- package/dist/styles.css +1 -1
- package/dist/theme.css +8 -1
- package/package.json +10 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,139 @@ Versions follow [semver](https://semver.org/). Through `0.x`, breaking changes m
|
|
|
5
5
|
|
|
6
6
|
> 🇹🇷 Türkçe için [tamga.org.tr/tr](https://tamga.org.tr/tr).
|
|
7
7
|
|
|
8
|
+
## 0.7.0
|
|
9
|
+
|
|
10
|
+
> A calendar can say no and carry the hour; a section can be told whether it is open; a label can
|
|
11
|
+
> carry the user's colour; the settings page has a bottom; the image field can talk to a server.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`DatePicker` / `Calendar` `min` and `max`** (`DateISO`). Days outside the range are disabled
|
|
16
|
+
and announced as such, the arrow keys skip them, and a month button closes when the whole
|
|
17
|
+
month is out of reach. A product had fallen back to `<input type="date" min=…>` for "not before
|
|
18
|
+
today"; that field looks different in every browser and lets the user type a date the kit had
|
|
19
|
+
decided should only be picked.
|
|
20
|
+
- **Calendar keyboard and grid semantics.** One day is in the tab order; the arrows move between
|
|
21
|
+
days (±1, ±7) across month edges, `Home`/`End` to the week's edges, `PageUp`/`PageDown` across
|
|
22
|
+
months; disabled days are skipped and the stop never lands on one (it follows the selected
|
|
23
|
+
day, else the first open day, and moves with an external `value`). `role="grid"` with rows
|
|
24
|
+
and `gridcell`s, every day named with its full date, `aria-selected` on the chosen day, native
|
|
25
|
+
`disabled` only.
|
|
26
|
+
- **`DateTimePicker`.** Date plus time, picked not typed: a calendar and hour/minute selects
|
|
27
|
+
(AM/PM only in 12-hour locales; the words from `Intl`, else `labels.am`/`labels.pm`, never
|
|
28
|
+
the kit's own English). The value is `DateTimeISO`, an instant written with the offset of the
|
|
29
|
+
picker's `timeZone` at that instant, `2026-04-15T14:30:00+03:00`; send
|
|
30
|
+
`new Date(v).toISOString()` to the server. A separate component rather than a prop: the
|
|
31
|
+
value's shape is different, and an `onChange` whose type depends on a prop is a trap.
|
|
32
|
+
`labels` adds `hour`, `minute`, `period`.
|
|
33
|
+
- `timeZone` (IANA) on `Calendar`, `DatePicker` and `DateTimePicker`. Wall clock and instant
|
|
34
|
+
meet through `Intl`; "today", `min`/`max`, the field's text and the emitted offset all use
|
|
35
|
+
that zone, never the browser's. Give it explicitly in a server-rendered app. DST: an hour
|
|
36
|
+
inside a spring gap is not offered and a wall time in it is pushed forward; an autumn
|
|
37
|
+
ambiguous hour is written with the later, standard offset, while a value coming from
|
|
38
|
+
outside keeps its own offset, also when only its minute changes. Checked against an
|
|
39
|
+
independent oracle over the 2026 transitions of 418 zones.
|
|
40
|
+
- Every emitted value is inside `[min, max]`: day, hour, minute and AM/PM changes are clamped
|
|
41
|
+
to the nearest valid time and the selects follow; hours with no valid minute are not
|
|
42
|
+
offered. A stored minute off the `minuteStep` grid, or a stored value outside the range, is
|
|
43
|
+
shown as it is rather than rounded or hidden: the field never lies about a stored value.
|
|
44
|
+
The hour boxes stay locked until a day is picked, so an hour alone never emits today.
|
|
45
|
+
`minuteStep` below 1 reads as 1.
|
|
46
|
+
- `id` and `invalid` on both pickers, like `Input`. An invalid `timeZone` falls back to the
|
|
47
|
+
browser's and an unparseable `value` / `min` / `max` reads as empty, with a console warning
|
|
48
|
+
in development, instead of a `RangeError`.
|
|
49
|
+
- **`Collapsible` controlled mode** · `open` + `onOpenChange(open)`. With `open` the product
|
|
50
|
+
holds the state and a click only reports; without it the section keeps its own, as before.
|
|
51
|
+
Both given, `open` wins and `defaultOpen` is ignored (React's contract). A product had rewritten
|
|
52
|
+
the row by hand because it needed to know when a row opened: it fetches on open.
|
|
53
|
+
- **`title` takes a node.** "Accessibility · /pricing" with the path faint; the accessible
|
|
54
|
+
name is `title` and `meta` together.
|
|
55
|
+
- **`note`** · a quiet line that stays visible while the section is closed, between head and
|
|
56
|
+
body, inside the same frame, bound to the head with `aria-describedby`: why a measurement
|
|
57
|
+
did not count, when it last ran.
|
|
58
|
+
- **`unmountOnClose`** · off by default (the body stays in the DOM, `hidden`); on, a heavy body
|
|
59
|
+
is not rendered while closed and `aria-controls` is omitted meanwhile.
|
|
60
|
+
- The caret's turn honours `prefers-reduced-motion`.
|
|
61
|
+
- **`Tag` `swatch`** · the user's own colour as a small mark before the text, `aria-hidden`.
|
|
62
|
+
Hex only (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`); anything else draws no mark, so user data
|
|
63
|
+
reaches `style` only as a validated value. This is the one **written** exception to Law 3
|
|
64
|
+
("colour is for deviation and interaction"): the colour is the user's data, an identity mark,
|
|
65
|
+
never a ground or a text colour, never status. The ring around the mark derives from the ink
|
|
66
|
+
(from the page colour inside `look="solid"`), so a near-white or near-black choice stays
|
|
67
|
+
visible in both themes. Written into the physics page and `docs/gerekce/06`, so nobody deletes
|
|
68
|
+
it as a violation or cites it as a precedent. Without `swatch` the output is unchanged.
|
|
69
|
+
- **`ColorMark`** · the mark on its own, on the kit's `tamga-mark` family, for a clickable
|
|
70
|
+
suggestion inside a `Button`.
|
|
71
|
+
- **`ColorSwatches` `noneLabel`** · a "no colour" option; an empty value selects it instead
|
|
72
|
+
of lighting the custom box.
|
|
73
|
+
- **`DangerZone` block** (`tamga-ui/blocks`) · the irreversible actions at the bottom of a
|
|
74
|
+
settings page. A title, an optional description and one or more action rows: an optional
|
|
75
|
+
name, one sentence, a `danger` button. `disabledReason` closes a row and shows the reason as
|
|
76
|
+
**visible text** bound to the button with `aria-describedby` · a disabled button is not a
|
|
77
|
+
reason, and a tooltip on one is invisible to most browsers, the keyboard and touch. `warning`
|
|
78
|
+
takes the kit's `Alert` when a precondition stopped the action; `busy` + `busyLabel` keep the
|
|
79
|
+
button named while it runs. The block never opens a confirm dialog: the click is the caller's,
|
|
80
|
+
the product builds `ConfirmDialog`. Edge and glyph tile in the critical tone, not the whole
|
|
81
|
+
card; the heading and the warning glyph say "danger" without colour. `data-action` sits on the
|
|
82
|
+
button, `data-key` on the row; every row takes `data-*`. A product had hand-written this zone
|
|
83
|
+
three ways on three pages.
|
|
84
|
+
- **`ImageField` upload path** · `onUpload(file) => Promise<string | false>` and
|
|
85
|
+
`onRemove() => Promise<boolean | void>`. Given `onUpload`, the field no longer converts to a
|
|
86
|
+
data URL: it hands the `File` to the caller and carries the states. Waiting: the input and
|
|
87
|
+
every button lock, the button is named by `labels.uploading` / `labels.removing`. Failure: the
|
|
88
|
+
rejected reason (a string, an `Error`, or `{ message }`) under the field in a `role="alert"`
|
|
89
|
+
region, shown as it is, so map a raw network error to your own words first; an empty reason
|
|
90
|
+
shows the new `labels.errorUpload`. The chosen file and its preview stay, "confirm" retries.
|
|
91
|
+
Resolving `onUpload` with `false` keeps the kit silent, for a caller that already handled the
|
|
92
|
+
value or the error (a plan-wall dialog); any other non-string result warns in development,
|
|
93
|
+
because a forgotten `return` must never read as "uploaded". `onRemove` resolving `false` is
|
|
94
|
+
silent the same way. Success: the returned address goes to `onChange`, and a quiet inline
|
|
95
|
+
`role="status"` line (`labels.uploaded` / `labels.removed`). Both live regions are in the DOM
|
|
96
|
+
from the start, only their text changes. `confirm` previews the pick and waits for a click
|
|
97
|
+
(for a logo); the default uploads on pick (for an avatar). `accept` and `maxBytes` are
|
|
98
|
+
checked on both paths before anything else runs, an empty file is refused with
|
|
99
|
+
`errorUnreadable`; `disabled` + `lockedNote` show the reason as visible text wired to the
|
|
100
|
+
buttons with `aria-describedby`. The preview is an object URL, revoked on cancel, on a new
|
|
101
|
+
pick, on success, when `value` changes from outside, and on unmount; a result arriving after
|
|
102
|
+
unmount or after an outside `value` change is dropped, while the caller's own `onChange` with
|
|
103
|
+
the same result (a cache update inside `onUpload`) is not "outside". `maxEdge` is optional in
|
|
104
|
+
the type: not used with `onUpload`, and without either the image is kept at its own size.
|
|
105
|
+
`labels.empty` takes a `ReactNode`. The pre-existing synchronous path (no `onUpload`) renders
|
|
106
|
+
the same markup, locked by test; its only change is that a file outside `accept` is refused
|
|
107
|
+
before `prepareImage` (the input's own list said so already). A product had written this
|
|
108
|
+
field four times.
|
|
109
|
+
- **`LogoTile` `surface`** · `"auto" | "light" | "dark"`, default `auto` (today's behaviour,
|
|
110
|
+
follows the page). `dark` draws the dark theme's ground even on a light page, for a logo drawn
|
|
111
|
+
for dark; `light` the reverse. Ground, edge and initial come as a fixed trio of tokens
|
|
112
|
+
(`--color-logo-ground/edge/ink-light|dark`), identical in both themes, so the tile reads the
|
|
113
|
+
same at night. Measured: initial 6.6:1 on light, 5.9:1 on dark; edge 3.9:1 on dark
|
|
114
|
+
(WCAG 1.4.11). A product that had been wrapping the tile in a local `.dark` class to get this
|
|
115
|
+
can drop the wrapper · note the ground differs by one step: the new dark surface is the dark
|
|
116
|
+
theme's **card** (`#16233a`), the old wrapper with `bg-page` gave the dark **page** (`#101a2c`).
|
|
117
|
+
Not pixel-identical; the card is what a tile sits on.
|
|
118
|
+
- **`Select` `disabled`** · locks the trigger, and looks locked (sunk ground, dashed edge, faint
|
|
119
|
+
ink).
|
|
120
|
+
- `check-item-hooks` also reads `actions` lists, and no longer mistakes `=>` for a closing
|
|
121
|
+
bracket (it was cutting item types short before their `Record<string, unknown>`).
|
|
122
|
+
|
|
123
|
+
### Changed
|
|
124
|
+
|
|
125
|
+
- The calendar's keyboard model: one tab stop and arrow keys instead of a tab stop on every day.
|
|
126
|
+
A `DatePicker` without `min`/`max` renders the same, but Tab now leaves the grid after one
|
|
127
|
+
stop; the arrows move inside it.
|
|
128
|
+
|
|
129
|
+
### Fixed
|
|
130
|
+
|
|
131
|
+
- **`DatePicker` `disabled`** left the clear and open buttons live: a locked date could be
|
|
132
|
+
cleared. Both buttons are disabled with the field.
|
|
133
|
+
- **`Button` busy without `busyLabel`** had no accessible name: the label was `invisible` and
|
|
134
|
+
`aria-hidden`. The button's own text now stays in the tree as sr-only, as the docs claimed.
|
|
135
|
+
- **Props tables on the docs site** · `ErrorState` lacked `onRetry` / `retryLabel` (a
|
|
136
|
+
parenthesised union was not read), `Button` lacked `busy` / `busyLabel` and `Input` `leading`
|
|
137
|
+
(an alias body was cut at its first `;`), and tables carried rows from types named inside a
|
|
138
|
+
prop (`AppShell` showed `NavEntry`'s fields). Only top-level intersections expand now, and a
|
|
139
|
+
row type such as `DangerAction` gets its own table.
|
|
140
|
+
|
|
8
141
|
## 0.6.0
|
|
9
142
|
|
|
10
143
|
> Sections fold; the one you are in never does.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* TEHLİKELİ BÖLGE · ayarlar sayfasının en altı, geri alınamayan eylemler.
|
|
4
|
+
*
|
|
5
|
+
* Üç ayar sayfası aynı bölgeyi üç ayrı biçimde elle yazıyordu; birinde kapalı
|
|
6
|
+
* düğmenin gerekçesi yalnız `title=` ipucundaydı. Blok onay penceresi AÇMAZ:
|
|
7
|
+
* tık çağırana gider, `ConfirmDialog`u ürün kurar · "adını yazdır" gibi
|
|
8
|
+
* kararlar onda kalır. Gerekçe: docs/gerekce/08-blok-ve-sablon.md
|
|
9
|
+
*/
|
|
10
|
+
export type DangerAction = {
|
|
11
|
+
key: string;
|
|
12
|
+
/**
|
|
13
|
+
* The row's title. Optional: when the button's label already names the action ("Delete the
|
|
14
|
+
* customer"), a title would repeat it, and three products had none. TR: Satırın başlığı.
|
|
15
|
+
* İsteğe bağlı: düğmenin metni eylemi zaten adlandırıyorsa ("Müşteriyi sil") başlık onu
|
|
16
|
+
* tekrarlardı, ve üç ürünün hiçbirinde yoktu.
|
|
17
|
+
*/
|
|
18
|
+
title?: string;
|
|
19
|
+
/** One sentence saying what will happen. TR: Ne olacağını söyleyen tek cümle. */
|
|
20
|
+
description: string;
|
|
21
|
+
/** The button's text. TR: Düğmenin metni. */
|
|
22
|
+
label: string;
|
|
23
|
+
/** The click is the caller's; open your `ConfirmDialog` here. TR: Tık çağıranın; `ConfirmDialog`unu burada aç. */
|
|
24
|
+
onClick?: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* Given, the row is closed and THIS SENTENCE is shown next to the button and bound to it with
|
|
27
|
+
* `aria-describedby`. A disabled button is not a reason; a tooltip on one is invisible to most
|
|
28
|
+
* browsers, to the keyboard and to touch. TR: Verilirse satır kapalı ve BU CÜMLE düğmenin
|
|
29
|
+
* yanında görünür, `aria-describedby` ile ona bağlı. Kapalı düğme bir gerekçe değildir; üstündeki
|
|
30
|
+
* ipucu çoğu tarayıcıda, klavyede ve dokunmatikte görünmez.
|
|
31
|
+
*/
|
|
32
|
+
disabledReason?: string;
|
|
33
|
+
/** The action is running: the button shows a spinner and locks. TR: Eylem sürüyor: düğme spinner gösterir ve kilitlenir. */
|
|
34
|
+
busy?: boolean;
|
|
35
|
+
/** The button's accessible name while busy ("Deleting…"); without it the name would be empty. TR: Sürerken düğmenin erişilebilir adı ("Siliniyor…"); yoksa ad boş kalırdı. */
|
|
36
|
+
busyLabel?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Shown under the row when a precondition stopped the action: the server said "do this first".
|
|
39
|
+
* Put the kit's `Alert` (`state="caution"`) here. TR: Bir önkoşul eylemi durdurduğunda satırın
|
|
40
|
+
* altında · sunucu "önce şunu yap" dedi. Kitin `Alert`ini (`state="caution"`) koy.
|
|
41
|
+
*/
|
|
42
|
+
warning?: ReactNode;
|
|
43
|
+
} & Record<string, unknown>;
|
|
44
|
+
export declare function DangerZone({ title, description, actions, className, ...rest }: {
|
|
45
|
+
/** The zone's heading; names the region for assistive tech. TR: Bölgenin başlığı; yardımcı teknoloji için bölgeyi adlandırır. */
|
|
46
|
+
title: string;
|
|
47
|
+
/** One line under the heading: "Nothing here can be undone." TR: Başlığın altında tek satır: "Buradaki işlemler geri alınamaz." */
|
|
48
|
+
description?: string;
|
|
49
|
+
actions: readonly DangerAction[];
|
|
50
|
+
className?: string;
|
|
51
|
+
/** `data-*` hooks pass through. TR: `data-*` kancaları geçiyor. */
|
|
52
|
+
[k: `data-${string}`]: unknown;
|
|
53
|
+
}): import("react").JSX.Element;
|
|
54
|
+
//# sourceMappingURL=danger-zone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"danger-zone.d.ts","sourceRoot":"","sources":["../../src/blocks/danger-zone.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAO9C;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,kHAAkH;IAClH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4HAA4H;IAC5H,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8KAA8K;IAC9K,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,OAAO,EACP,SAAS,EACT,GAAG,IAAI,EACR,EAAE;IACD,iIAAiI;IACjI,KAAK,EAAE,MAAM,CAAC;IACd,mIAAmI;IACnI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,CAAC,CAAC,EAAE,QAAQ,MAAM,EAAE,GAAG,OAAO,CAAC;CAChC,+BAsDA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useId } from "react";
|
|
5
|
+
import { Button } from "../components/button.js";
|
|
6
|
+
import { Icon } from "../components/icon.js";
|
|
7
|
+
import { Warning } from "../components/icons.js";
|
|
8
|
+
import { cn } from "../lib/cn.js";
|
|
9
|
+
import { dataProps } from "../lib/data-props.js";
|
|
10
|
+
export function DangerZone({ title, description, actions, className, ...rest }) {
|
|
11
|
+
const id = useId();
|
|
12
|
+
return (_jsxs("section", { ...dataProps(rest), className: cn("tamga-danger-zone", className), "aria-labelledby": `${id}-b`, children: [_jsxs("div", { className: "tamga-danger-zone-head", children: [_jsx("span", { className: "tamga-danger-zone-tile", "aria-hidden": "true", children: _jsx(Icon, { icon: Warning, size: "sm", weight: "bold" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h3", { id: `${id}-b`, className: "text-subhead font-semibold text-ink", children: title }), description ? _jsx("p", { className: "mt-1 text-small leading-relaxed text-ink-soft", children: description }) : null] })] }), _jsx("ul", { className: "tamga-danger-zone-list", children: actions.map(({ key, title: ad, description: ne, label, onClick, disabledReason, busy, busyLabel, warning, ...satir }, i) => {
|
|
13
|
+
const gerekceId = `${id}-g${i}`;
|
|
14
|
+
return (_createElement("li", { ...dataProps(satir), key: key, "data-key": key, "data-disabled": disabledReason ? "true" : undefined, className: "tamga-danger-zone-row" },
|
|
15
|
+
_jsxs("div", { className: "flex flex-wrap items-start gap-4", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [ad ? _jsx("p", { className: "text-body font-semibold text-ink", children: ad }) : null, _jsx("p", { className: ad ? "text-small leading-relaxed text-ink-soft" : "text-body leading-relaxed text-ink", children: ne }), disabledReason ? (_jsx("p", { id: gerekceId, className: "tamga-danger-zone-reason", "data-reason": true, children: disabledReason })) : null] }), _jsx(Button, { variant: "danger", size: "sm", "data-action": key, onClick: onClick, disabled: Boolean(disabledReason) || busy, busy: busy, busyLabel: busyLabel, "aria-describedby": disabledReason ? gerekceId : undefined, children: label })] }),
|
|
16
|
+
warning ? _jsx("div", { className: "mt-3", children: warning }) : null));
|
|
17
|
+
}) })] }));
|
|
18
|
+
}
|
package/dist/blocks/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@
|
|
|
8
8
|
export { FilterBar, rangeStart, rangeEnd, type FilterBarLabels, type FilterBarProps, type FilterField, type FilterFieldKind, type FilterGroup, type FilterValues, } from "./filter-bar.js";
|
|
9
9
|
export { SaveBar } from "./save-bar.js";
|
|
10
10
|
export { CountRow } from "./count-row.js";
|
|
11
|
+
export { DangerZone, type DangerAction } from "./danger-zone.js";
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,SAAS,EACT,UAAU,EACV,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,SAAS,EACT,UAAU,EACV,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/blocks/index.js
CHANGED
|
@@ -9,14 +9,23 @@ export type SwatchOption = {
|
|
|
9
9
|
* Brand colour chosen from a few squares, or written by hand.
|
|
10
10
|
* TR: Birkaç kareden seçilen ya da elle yazılan marka rengi.
|
|
11
11
|
*/
|
|
12
|
-
export declare function ColorSwatches({ options, value, onChange, customLabel, custom, className, ...rest }: {
|
|
12
|
+
export declare function ColorSwatches({ options, value, onChange, customLabel, custom, noneLabel, className, ...rest }: {
|
|
13
13
|
options: readonly SwatchOption[];
|
|
14
|
+
/** The chosen hex; `""` means "no colour" when `noneLabel` is given. TR: Seçili hex; `noneLabel` verilmişse `""` "renk yok" demek. */
|
|
14
15
|
value: string;
|
|
16
|
+
/** Fires with the hex, or `""` for the "no colour" box. TR: Hex ile ateşlenir; "renk yok" kutusu için `""`. */
|
|
15
17
|
onChange: (hex: string) => void;
|
|
16
18
|
/** Accessible name of the "my own colour" control. TR: "Kendi rengim" kontrolünün erişilebilir adı. */
|
|
17
19
|
customLabel: string;
|
|
18
20
|
/** Whether a free colour is offered at all. TR: Serbest rengin hiç sunulup sunulmadığı. */
|
|
19
21
|
custom?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Given, a first box means "no colour" and an empty `value` selects it. Without it an empty
|
|
24
|
+
* value lit the custom box, so an optional colour read as a choice. TR: Verilirse ilk kutu
|
|
25
|
+
* "renk yok" demek ve boş `value` onu seçer. Yokken boş değer "kendi rengim" kutusunu
|
|
26
|
+
* yakıyordu, isteğe bağlı bir renk bir seçim gibi okunuyordu.
|
|
27
|
+
*/
|
|
28
|
+
noneLabel?: string;
|
|
20
29
|
className?: string;
|
|
21
30
|
/** `data-*` hooks pass through. TR: `data-*` kancaları geçiyor. */
|
|
22
31
|
[k: `data-${string}`]: unknown;
|
|
@@ -88,27 +97,104 @@ export declare function RailCards({ value, onChange, labels, className, ...rest
|
|
|
88
97
|
* offers reorder arrows with nothing to reorder.
|
|
89
98
|
* TR: `FileUpload` DEĞİL: o bir GALERİ (çoklu seçim, sıralama okları, "kapak"
|
|
90
99
|
* rozeti). Ürün fotoğrafları için doğru, tek bir logo için yanlış.
|
|
100
|
+
*
|
|
101
|
+
* Two paths, one field. Without `onUpload` the image is shrunk in the browser
|
|
102
|
+
* and a data URL goes to `onChange`. With `onUpload` the file goes to the
|
|
103
|
+
* caller and the field carries the states: waiting, failure, a quiet
|
|
104
|
+
* confirmation. TR: İki yol, tek alan. `onUpload` yokken görsel tarayıcıda
|
|
105
|
+
* küçültülür, data-URL `onChange`'e gider. `onUpload` verilince dosya çağırana
|
|
106
|
+
* gider; alan bekleme, hata ve sessiz onayı taşır.
|
|
107
|
+
*
|
|
108
|
+
* Gerekçe: docs/gerekce/01-form-ve-girdi.md
|
|
91
109
|
*/
|
|
92
|
-
export declare function ImageField({ value, onChange, maxEdge, labels, preview, extra, className, ...rest }: {
|
|
110
|
+
export declare function ImageField({ value, onChange, maxEdge, labels, preview, extra, onUpload, onRemove, confirm, accept, maxBytes, disabled, lockedNote, className, ...rest }: {
|
|
93
111
|
value: string | null;
|
|
94
112
|
onChange: (next: string | null) => void;
|
|
95
|
-
/**
|
|
96
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Longest edge of the stored image, in pixels; the browser path shrinks to it, so give it
|
|
115
|
+
* there. Not used with `onUpload`: the server decides. TR: Saklanan görselin en uzun kenarı,
|
|
116
|
+
* piksel; tarayıcı yolu ona küçültür, orada ver. `onUpload` ile kullanılmaz: sunucu karar verir.
|
|
117
|
+
*/
|
|
118
|
+
maxEdge?: number;
|
|
97
119
|
labels: {
|
|
98
120
|
/** "Logo", "Mark": used inside the buttons. TR: "Logo", "Amblem": düğmelerin içinde geçiyor. */
|
|
99
121
|
name: string;
|
|
100
122
|
upload: string;
|
|
101
123
|
replace: string;
|
|
102
124
|
remove: string;
|
|
103
|
-
empty:
|
|
125
|
+
/** The empty state's content: a sentence, or a node. TR: Boş durumun içeriği: bir cümle ya da bir düğüm. */
|
|
126
|
+
empty: ReactNode;
|
|
104
127
|
errorType: string;
|
|
105
128
|
errorSize: string;
|
|
106
129
|
errorUnreadable: string;
|
|
130
|
+
/**
|
|
131
|
+
* Shown when `onUpload` or `onRemove` rejects without a message (an empty string, `undefined`);
|
|
132
|
+
* falls back to `errorUnreadable`. TR: `onUpload` ya da `onRemove` mesajsız reddedince (boş
|
|
133
|
+
* metin, `undefined`); yoksa `errorUnreadable`.
|
|
134
|
+
*/
|
|
135
|
+
errorUpload?: string;
|
|
136
|
+
/** The button's name while `onUpload` runs; falls back to `upload`. TR: `onUpload` sürerken düğmenin adı; yoksa `upload`. */
|
|
137
|
+
uploading?: string;
|
|
138
|
+
/** The button's name while `onRemove` runs; falls back to `remove`. TR: `onRemove` sürerken düğmenin adı; yoksa `remove`. */
|
|
139
|
+
removing?: string;
|
|
140
|
+
/** The quiet line after a successful upload; omitted, nothing is said. TR: Başarılı yüklemeden sonraki sessiz satır; yoksa susar. */
|
|
141
|
+
uploaded?: string;
|
|
142
|
+
/** The quiet line after a successful removal. TR: Başarılı kaldırmadan sonraki sessiz satır. */
|
|
143
|
+
removed?: string;
|
|
144
|
+
/** The button that sends a previewed pick; falls back to `upload`. TR: Önizlenen seçimi gönderen düğme; yoksa `upload`. */
|
|
145
|
+
confirm?: string;
|
|
146
|
+
/** The button that drops a previewed pick; falls back to `remove`. TR: Önizlenen seçimi bırakan düğme; yoksa `remove`. */
|
|
147
|
+
cancel?: string;
|
|
107
148
|
};
|
|
108
149
|
/** The caller draws the preview: a logo is wide, a mark is square. TR: Önizlemeyi çağıran çiziyor: logo geniş, amblem kare. */
|
|
109
150
|
preview: (src: string) => ReactNode;
|
|
110
151
|
/** An extra control beside the buttons. TR: Düğmelerin yanına giren ek kontrol. */
|
|
111
152
|
extra?: ReactNode;
|
|
153
|
+
/**
|
|
154
|
+
* Hands the chosen file to the caller instead of converting it. Resolve with the stored
|
|
155
|
+
* address: it goes to `onChange` and `labels.uploaded` is announced. Resolve with `false` when
|
|
156
|
+
* you handled the result yourself (a plan-wall dialog, your own `onChange`): the kit stays
|
|
157
|
+
* silent. Anything else is a bug and warns in development, because a forgotten `return` must
|
|
158
|
+
* not read as "uploaded". Reject with a string, an `Error` or `{ message }`: that text is shown
|
|
159
|
+
* under the field AS IT IS, so map a raw network error to your own words before rejecting; an
|
|
160
|
+
* empty reason shows `labels.errorUpload`. The file and its preview stay, "confirm" retries. A
|
|
161
|
+
* result that arrives after `value` changed from outside, or after unmount, is dropped. TR:
|
|
162
|
+
* Seçilen dosyayı dönüştürmek yerine çağırana verir. Saklanan adresle çözülür: `onChange`'e
|
|
163
|
+
* gider, `labels.uploaded` duyurulur. Sonucu kendin ele aldıysan `false` ile çöz: kit susar.
|
|
164
|
+
* Başka bir değer geliştirmede uyarır; unutulan bir `return` "yüklendi" sayılmaz. Metin, `Error`
|
|
165
|
+
* ya da `{ message }` ile reddedilirse metin alanın altında OLDUĞU GİBİ gösterilir, ağ hatasını
|
|
166
|
+
* reddetmeden önce kendi sözüne çevir; boş sebep `labels.errorUpload` gösterir. Dosya ve
|
|
167
|
+
* önizleme kalır, "confirm" yeniden dener. `value` dışarıdan değiştikten ya da bileşen
|
|
168
|
+
* kalktıktan sonra gelen sonuç atılır.
|
|
169
|
+
*/
|
|
170
|
+
onUpload?: (file: File) => Promise<string | false>;
|
|
171
|
+
/**
|
|
172
|
+
* Awaited before `onChange(null)`; `false` keeps the kit silent (you handled it); a rejection
|
|
173
|
+
* shows under the field like `onUpload`'s. TR: `onChange(null)`'dan önce beklenir; `false` kiti
|
|
174
|
+
* susturur (sen ele aldın); red `onUpload`'daki gibi alanın altında.
|
|
175
|
+
*/
|
|
176
|
+
onRemove?: () => Promise<boolean | void>;
|
|
177
|
+
/**
|
|
178
|
+
* Preview first, send on a click: for a logo, a chance to notice the wrong file. Off, the pick
|
|
179
|
+
* uploads at once (an avatar). TR: Önce önizle, tıklayınca gönder: logo için yanlış dosyayı fark
|
|
180
|
+
* etme şansı. Kapalıyken seçim hemen gider (avatar).
|
|
181
|
+
*/
|
|
182
|
+
confirm?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* The input's accept list; the pick is checked against it on both paths, before `prepareImage`
|
|
185
|
+
* or `onUpload`. TR: Girdinin accept listesi; seçim iki yolda da `prepareImage` ya da
|
|
186
|
+
* `onUpload`'dan önce buna göre denetlenir.
|
|
187
|
+
*/
|
|
188
|
+
accept?: string;
|
|
189
|
+
/** Refused on both paths with `errorSize`; 8 MB by default. TR: İki yolda da `errorSize` ile reddedilir; varsayılan 8 MB. */
|
|
190
|
+
maxBytes?: number;
|
|
191
|
+
disabled?: boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Why the field is locked, as visible text under it and `aria-describedby` on the buttons; a
|
|
194
|
+
* tooltip hides from touch and keyboards. TR: Alanın neden kilitli olduğu, altında görünür
|
|
195
|
+
* metin ve düğmelerde `aria-describedby`; ipucu dokunmatikten ve klavyeden saklanır.
|
|
196
|
+
*/
|
|
197
|
+
lockedNote?: string;
|
|
112
198
|
className?: string;
|
|
113
199
|
/** `data-*` hooks pass through. TR: `data-*` kancaları geçiyor. */
|
|
114
200
|
[k: `data-${string}`]: unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appearance.d.ts","sourceRoot":"","sources":["../../src/components/appearance.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"appearance.d.ts","sourceRoot":"","sources":["../../src/components/appearance.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useId, useRef, useState } from "react";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
4
4
|
import { cn } from "../lib/cn.js";
|
|
5
5
|
import { makePalette } from "../lib/palette.js";
|
|
6
6
|
import { dataProps } from "../lib/data-props.js";
|
|
7
|
-
import { IMAGE_ACCEPT, prepareImage } from "../lib/image.js";
|
|
7
|
+
import { IMAGE_ACCEPT, RAW_LIMIT, prepareImage } from "../lib/image.js";
|
|
8
|
+
import { uyar } from "../lib/uyar.js";
|
|
8
9
|
import { Alert } from "./primitives.js";
|
|
9
10
|
import { Button } from "./button.js";
|
|
10
11
|
import { Icon } from "./icon.js";
|
|
11
|
-
import { Check, Eyedropper } from "./icons.js";
|
|
12
|
+
import { Check, Eyedropper, Close } from "./icons.js";
|
|
12
13
|
/**
|
|
13
14
|
* Brand colour chosen from a few squares, or written by hand.
|
|
14
15
|
* TR: Birkaç kareden seçilen ya da elle yazılan marka rengi.
|
|
15
16
|
*/
|
|
16
|
-
export function ColorSwatches({ options, value, onChange, customLabel, custom = true, className, ...rest }) {
|
|
17
|
+
export function ColorSwatches({ options, value, onChange, customLabel, custom = true, noneLabel, className, ...rest }) {
|
|
17
18
|
const secili = (hex) => hex.toLowerCase() === value.toLowerCase();
|
|
18
|
-
const
|
|
19
|
-
|
|
19
|
+
const yokSecili = noneLabel !== undefined && value === "";
|
|
20
|
+
const hazirMi = yokSecili || options.some((o) => secili(o.hex));
|
|
21
|
+
return (_jsxs("span", { ...dataProps(rest), className: cn("flex flex-wrap items-center gap-3", className), children: [noneLabel !== undefined ? (_jsx("button", { type: "button", className: "tamga-swatch tamga-swatch-none", "data-selected": yokSecili, "aria-pressed": yokSecili, "aria-label": noneLabel, title: noneLabel, onClick: () => onChange(""), children: yokSecili ? _jsx(Icon, { icon: Check, size: "base", weight: "bold" }) : _jsx(Icon, { icon: Close, size: "base", weight: "bold" }) })) : null, options.map(({ hex, label: etiket, ...rest }) => (_jsx("button", { ...rest, type: "button", className: "tamga-swatch", style: { background: hex, color: mürekkep(hex) }, "data-selected": secili(hex), "aria-pressed": secili(hex), "aria-label": etiket, title: etiket, onClick: () => onChange(hex), children: secili(hex) ? _jsx(Icon, { icon: Check, size: "base", weight: "bold" }) : null }, hex))), custom ? (_jsxs("label", { className: "tamga-swatch tamga-swatch-custom", "data-selected": !hazirMi, title: customLabel, style: hazirMi
|
|
20
22
|
? /* Hiçbir hazır renk seçili değilken kutu o rengi giyiyor;
|
|
21
23
|
seçiliyken damalı zemin "burada henüz bir renk yok" diyor. */
|
|
22
24
|
{ background: DAMA }
|
|
@@ -117,29 +119,173 @@ export function RailCards({ value, onChange, labels, className, ...rest }) {
|
|
|
117
119
|
* offers reorder arrows with nothing to reorder.
|
|
118
120
|
* TR: `FileUpload` DEĞİL: o bir GALERİ (çoklu seçim, sıralama okları, "kapak"
|
|
119
121
|
* rozeti). Ürün fotoğrafları için doğru, tek bir logo için yanlış.
|
|
122
|
+
*
|
|
123
|
+
* Two paths, one field. Without `onUpload` the image is shrunk in the browser
|
|
124
|
+
* and a data URL goes to `onChange`. With `onUpload` the file goes to the
|
|
125
|
+
* caller and the field carries the states: waiting, failure, a quiet
|
|
126
|
+
* confirmation. TR: İki yol, tek alan. `onUpload` yokken görsel tarayıcıda
|
|
127
|
+
* küçültülür, data-URL `onChange`'e gider. `onUpload` verilince dosya çağırana
|
|
128
|
+
* gider; alan bekleme, hata ve sessiz onayı taşır.
|
|
129
|
+
*
|
|
130
|
+
* Gerekçe: docs/gerekce/01-form-ve-girdi.md
|
|
120
131
|
*/
|
|
121
|
-
export function ImageField({ value, onChange, maxEdge, labels, preview, extra, className, ...rest }) {
|
|
132
|
+
export function ImageField({ value, onChange, maxEdge, labels, preview, extra, onUpload, onRemove, confirm = false, accept = IMAGE_ACCEPT, maxBytes = RAW_LIMIT, disabled = false, lockedNote, className, ...rest }) {
|
|
122
133
|
const id = useId();
|
|
123
134
|
const girdi = useRef(null);
|
|
124
135
|
const [hata, setHata] = useState(null);
|
|
136
|
+
const [bekleyen, setBekleyen] = useState(null);
|
|
137
|
+
const [mesgul, setMesgul] = useState(null);
|
|
138
|
+
const [onay, setOnay] = useState(null);
|
|
139
|
+
/* Adres ref'te de: kalkışta son adresi bırakmak için state okunamıyor. İstek
|
|
140
|
+
sayacı, "canlı" bayrağı ve son değer bayat cevabı ayırıyor: kalkıştan ya da
|
|
141
|
+
dış değer değişiminden sonra gelen sonuç ne onChange'e gider ne ekrana. */
|
|
142
|
+
const adres = useRef(null);
|
|
143
|
+
const istek = useRef(0);
|
|
144
|
+
const canli = useRef(true);
|
|
145
|
+
const sonDeger = useRef(value);
|
|
146
|
+
sonDeger.current = value;
|
|
147
|
+
function birak() {
|
|
148
|
+
if (adres.current)
|
|
149
|
+
URL.revokeObjectURL(adres.current);
|
|
150
|
+
adres.current = null;
|
|
151
|
+
setBekleyen(null);
|
|
152
|
+
}
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
canli.current = true;
|
|
155
|
+
return () => {
|
|
156
|
+
canli.current = false;
|
|
157
|
+
if (adres.current)
|
|
158
|
+
URL.revokeObjectURL(adres.current);
|
|
159
|
+
adres.current = null;
|
|
160
|
+
};
|
|
161
|
+
}, []);
|
|
162
|
+
/* Değer dışarıdan geldiyse bekleyen önizleme bayat kaldı. */
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (value && adres.current)
|
|
165
|
+
birak();
|
|
166
|
+
}, [value]);
|
|
167
|
+
const kilit = disabled || mesgul !== null;
|
|
168
|
+
/* Dış değer değiştiyse sonuç bayat · ama çağıranın KENDİ güncellemesi değil:
|
|
169
|
+
onUpload içinde önbelleği sonuçla, onRemove içinde null ile güncelleyen bir
|
|
170
|
+
çağıran "yüklendi"yi yine duymalı. */
|
|
171
|
+
const bayat = (benim, baslangic, beklenen) => !canli.current ||
|
|
172
|
+
istek.current !== benim ||
|
|
173
|
+
(sonDeger.current !== baslangic && (beklenen === undefined || sonDeger.current !== beklenen));
|
|
174
|
+
function sifirla() {
|
|
175
|
+
/* GİRDİ SIFIRLANIYOR: aynı dosya ikinci kez seçilince `change` hiç
|
|
176
|
+
çıkmıyor, çünkü değer değişmiyor. Kullanıcı için bu "ikinci denemede
|
|
177
|
+
çalışmıyor" demek. */
|
|
178
|
+
if (girdi.current)
|
|
179
|
+
girdi.current.value = "";
|
|
180
|
+
}
|
|
125
181
|
async function al(file) {
|
|
126
|
-
if (!file)
|
|
182
|
+
if (!file || kilit)
|
|
183
|
+
return;
|
|
184
|
+
sifirla();
|
|
185
|
+
setHata(null);
|
|
186
|
+
setOnay(null);
|
|
187
|
+
if (!kabul(file, accept))
|
|
188
|
+
return setHata(labels.errorType);
|
|
189
|
+
if (file.size === 0)
|
|
190
|
+
return setHata(labels.errorUnreadable);
|
|
191
|
+
if (file.size > maxBytes)
|
|
192
|
+
return setHata(labels.errorSize);
|
|
193
|
+
if (!onUpload) {
|
|
194
|
+
try {
|
|
195
|
+
onChange(await prepareImage(file, maxEdge ?? Infinity));
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
const kod = e instanceof Error ? e.message : "unreadable";
|
|
199
|
+
setHata(kod === "type" ? labels.errorType : kod === "size" ? labels.errorSize : labels.errorUnreadable);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
birak();
|
|
204
|
+
const url = URL.createObjectURL(file);
|
|
205
|
+
adres.current = url;
|
|
206
|
+
setBekleyen({ file, url });
|
|
207
|
+
if (!confirm)
|
|
208
|
+
await gonder(file);
|
|
209
|
+
}
|
|
210
|
+
async function gonder(file) {
|
|
211
|
+
if (!onUpload || mesgul)
|
|
212
|
+
return;
|
|
213
|
+
const benim = ++istek.current;
|
|
214
|
+
const baslangic = sonDeger.current;
|
|
215
|
+
setHata(null);
|
|
216
|
+
setOnay(null);
|
|
217
|
+
setMesgul("upload");
|
|
218
|
+
try {
|
|
219
|
+
const sonuc = await onUpload(file);
|
|
220
|
+
if (bayat(benim, baslangic, typeof sonuc === "string" ? sonuc : undefined))
|
|
221
|
+
return;
|
|
222
|
+
if (typeof sonuc === "string") {
|
|
223
|
+
birak();
|
|
224
|
+
onChange(sonuc);
|
|
225
|
+
setOnay(labels.uploaded ?? null);
|
|
226
|
+
}
|
|
227
|
+
else if (sonuc !== false) {
|
|
228
|
+
uyar("ImageField: onUpload resolved with neither an address nor `false`; nothing was announced. Return the stored address, or `false` when you handled the result yourself.");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
if (bayat(benim, baslangic, undefined))
|
|
233
|
+
return;
|
|
234
|
+
setHata(metin(e, labels));
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
if (canli.current)
|
|
238
|
+
setMesgul(null);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function kaldir() {
|
|
242
|
+
if (kilit)
|
|
127
243
|
return;
|
|
128
244
|
setHata(null);
|
|
245
|
+
setOnay(null);
|
|
246
|
+
if (!onRemove)
|
|
247
|
+
return onChange(null);
|
|
248
|
+
const benim = ++istek.current;
|
|
249
|
+
const baslangic = sonDeger.current;
|
|
250
|
+
setMesgul("remove");
|
|
129
251
|
try {
|
|
130
|
-
|
|
252
|
+
const sonuc = await onRemove();
|
|
253
|
+
if (bayat(benim, baslangic, null) || sonuc === false)
|
|
254
|
+
return;
|
|
255
|
+
onChange(null);
|
|
256
|
+
setOnay(labels.removed ?? null);
|
|
131
257
|
}
|
|
132
258
|
catch (e) {
|
|
133
|
-
|
|
134
|
-
|
|
259
|
+
if (bayat(benim, baslangic, undefined))
|
|
260
|
+
return;
|
|
261
|
+
setHata(metin(e, labels));
|
|
135
262
|
}
|
|
136
263
|
finally {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
çalışmıyor" demek. */
|
|
140
|
-
if (girdi.current)
|
|
141
|
-
girdi.current.value = "";
|
|
264
|
+
if (canli.current)
|
|
265
|
+
setMesgul(null);
|
|
142
266
|
}
|
|
143
267
|
}
|
|
144
|
-
|
|
268
|
+
const gosterilen = bekleyen?.url ?? value;
|
|
269
|
+
const asenkron = Boolean(onUpload || onRemove);
|
|
270
|
+
const notKimligi = `${id}-kilit`;
|
|
271
|
+
const tarif = disabled && lockedNote ? notKimligi : undefined;
|
|
272
|
+
return (_jsxs("div", { ...dataProps(rest), className: cn("flex flex-col gap-3", className), children: [_jsx("input", { ref: girdi, id: id, type: "file", accept: accept, className: "sr-only", disabled: kilit || undefined, onChange: (e) => al(e.target.files?.[0]) }), _jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [_jsx("span", { className: "tamga-image-field", "data-empty": gosterilen ? undefined : "true", children: gosterilen ? preview(gosterilen) : labels.empty }), _jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [bekleyen ? (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", busy: mesgul === "upload", busyLabel: labels.uploading ?? labels.upload, disabled: kilit || undefined, "aria-describedby": tarif, onClick: () => gonder(bekleyen.file), children: labels.confirm ?? labels.upload }), _jsx(Button, { type: "button", variant: "ghost", disabled: kilit || undefined, onClick: birak, children: labels.cancel ?? labels.remove })] })) : (_jsx(Button, { type: "button", busy: mesgul === "upload", busyLabel: labels.uploading ?? labels.upload, disabled: kilit || undefined, "aria-describedby": tarif, onClick: () => girdi.current?.click(), children: value ? labels.replace : labels.upload })), extra, value && !bekleyen ? (_jsx(Button, { type: "button", variant: "ghost", busy: mesgul === "remove", busyLabel: labels.removing ?? labels.remove, disabled: kilit || undefined, "aria-describedby": tarif, onClick: kaldir, children: labels.remove })) : null] })] }), lockedNote && disabled ? (_jsx("p", { id: notKimligi, className: "text-small text-ink-faint", children: lockedNote })) : null, asenkron ? (_jsx("p", { role: "status", className: cn("text-small text-ink-faint", !onay && "sr-only"), children: onay })) : null, asenkron ? (_jsx("div", { role: "alert", className: cn(!hata && "sr-only"), children: hata ? _jsx(Alert, { state: "danger", title: hata }) : null })) : hata ? (_jsx(Alert, { state: "danger", title: hata })) : null] }));
|
|
273
|
+
}
|
|
274
|
+
/** `accept` listesine karşı ön denetim: MIME, `image/*` ve `.png` biçimleri. Boş liste her şeyi geçirir. */
|
|
275
|
+
function kabul(file, accept) {
|
|
276
|
+
const liste = accept.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
277
|
+
if (liste.length === 0)
|
|
278
|
+
return true;
|
|
279
|
+
const tur = file.type.toLowerCase();
|
|
280
|
+
const ad = file.name.toLowerCase();
|
|
281
|
+
return liste.some((k) => k.startsWith(".") ? ad.endsWith(k) : k.endsWith("/*") ? tur.startsWith(k.slice(0, -1)) : tur === k);
|
|
282
|
+
}
|
|
283
|
+
/** Çağıranın reddi: metin, `Error` ya da `{ message }` olduğu gibi; boşsa kitin genel sözü. */
|
|
284
|
+
function metin(e, labels) {
|
|
285
|
+
const yedek = labels.errorUpload ?? labels.errorUnreadable;
|
|
286
|
+
if (typeof e === "string")
|
|
287
|
+
return e || yedek;
|
|
288
|
+
if (e && typeof e === "object" && "message" in e && typeof e.message === "string" && e.message)
|
|
289
|
+
return e.message;
|
|
290
|
+
return yedek;
|
|
145
291
|
}
|
|
@@ -26,7 +26,8 @@ export type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof b
|
|
|
26
26
|
*
|
|
27
27
|
* ZORUNLU DEĞİL AMA VARSAYILANI YOK: kit çeviri yapmıyor, ve "Loading"
|
|
28
28
|
* diye İngilizce bir varsayılan her Türkçe panele İngilizce bir cümle
|
|
29
|
-
* gönderirdi. Verilmezse düğmenin kendi metni
|
|
29
|
+
* gönderirdi. Verilmezse düğmenin kendi metni ad olarak kalıyor (görünmez
|
|
30
|
+
* ama okunur); adsız bir düğme olmuyor.
|
|
30
31
|
*/
|
|
31
32
|
busyLabel?: string;
|
|
32
33
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/components/button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAIlE;;;;GAIG;AAEH,eAAO,MAAM,cAAc;;;;8EAwCzB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GACtD,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG;IACpC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/components/button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAIlE;;;;GAIG;AAEH,eAAO,MAAM,cAAc;;;;8EAwCzB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GACtD,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG;IACpC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEJ,wBAAgB,MAAM,CAAC,EACrB,SAAS,EACT,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,IAAY,EACZ,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,WAAW,+BA8Bb;AAID,eAAO,MAAM,kBAAkB;;;8EAoB7B,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,IAAI,EACJ,OAAO,EACP,YAAY,EAAE,KAAK,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GAC/B,YAAY,CAAC,OAAO,kBAAkB,CAAC,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,+BASnE;AAED,eAAO,MAAM,kBAAkB;;8EAW7B,CAAC;AAEH,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,OAAO,EACP,YAAY,EAAE,KAAK,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GAC/B,YAAY,CAAC,OAAO,kBAAkB,CAAC,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,+BASnE"}
|