tamga-ui 0.2.0 → 0.2.1

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 CHANGED
@@ -5,6 +5,44 @@ 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.2.1
9
+
10
+ ### Added
11
+
12
+ - **`AppearanceTemplate`** (`tamga-ui/patterns`) — the whole appearance screen: logo, mark, brand
13
+ colour, theme and sidebar width. This screen had been built twice, by hand, in two products, and
14
+ the second one came out bare: a hex field instead of swatches, three words instead of three
15
+ pictures, no logo area at all. Handing over the parts and saying "arrange them yourself" is what
16
+ let them drift; that is the job the pattern layer exists for (ADR-0004).
17
+
18
+ It stores NOTHING and applies NOTHING. `value` in, `onChange` and `onSave` out, every word from
19
+ `labels`. Where the preference lives (session, account, browser) is a product decision, and
20
+ turning the chosen colour into tokens stays the product's call too, because the root element is
21
+ its own.
22
+
23
+ - **`ColorSwatches`, `ThemeCards`, `ImageField`** — the parts, for a screen that needs a different
24
+ arrangement. `ThemeCards` draws a miniature of each option rather than naming it; a theme is not
25
+ chosen by a word, it is chosen by seeing the result.
26
+ - **`SquarePicker`** — dragging a square over an uploaded logo to cut a mark out of it. A mark
27
+ cannot be derived automatically, and this is the honest alternative: the human says where it is.
28
+ - **`prepareImage` and `cropSquare`** (`tamga-ui`) — resize and square-crop an uploaded file, with
29
+ no library: a `<canvas>`. The ratio is kept and nothing is cropped silently; SVG passes through
30
+ untouched, because baking a vector into a 512px PNG stops it being sharp.
31
+ - **`SettingsPanel.accent`** — a coloured left edge on a section block.
32
+
33
+ ### Fixed
34
+
35
+ - **Completed steps were darker than everything else on the screen.** `StepStrip` filled them with
36
+ `--color-accent-line`, which is the accent's TEXT variant: it is derived by SEARCHING for 4.5
37
+ contrast against the page, so it is always darker than the face. Every button and badge on the
38
+ same screen used `--color-accent`, and the gap widened with each new brand colour. The ink on top
39
+ was measured against `accent`, never against `accent-line`, so that pairing was never checked at
40
+ all. Filled accent objects now follow the button's contract everywhere: `accent` for the fill,
41
+ `accent-shadow` for the edge, `accent-ink` for the ink.
42
+
43
+ Nothing errored here: the colours were valid and the contrast gates passed. `check-css` now
44
+ refuses `--color-accent-line` as a fill, so this class of mistake cannot pass silently again.
45
+
8
46
  ## 0.2.0
9
47
 
10
48
  Most of this release came out of gaps found by two people using the kit **from the outside**:
@@ -0,0 +1,78 @@
1
+ import { type ReactNode } from "react";
2
+ export type SwatchOption = {
3
+ /** The hex the swatch applies. TR: Kutunun uyguladığı hex. */
4
+ hex: string;
5
+ /** Its name, translated, used as the accessible label. TR: Çevrilmiş adı; erişilebilir ad olarak kullanılıyor. */
6
+ label: string;
7
+ };
8
+ /**
9
+ * Brand colour chosen from a few squares, or written by hand.
10
+ * TR: Birkaç kareden seçilen ya da elle yazılan marka rengi.
11
+ */
12
+ export declare function ColorSwatches({ options, value, onChange, customLabel, custom, className, }: {
13
+ options: readonly SwatchOption[];
14
+ value: string;
15
+ onChange: (hex: string) => void;
16
+ /** Accessible name of the "my own colour" control. TR: "Kendi rengim" kontrolünün erişilebilir adı. */
17
+ customLabel: string;
18
+ /** Whether a free colour is offered at all. TR: Serbest rengin hiç sunulup sunulmadığı. */
19
+ custom?: boolean;
20
+ className?: string;
21
+ }): import("react").JSX.Element;
22
+ export type ThemeChoice = "light" | "dark" | "system";
23
+ /**
24
+ * Theme picked from three little pictures of the panel.
25
+ * TR: Panelin üç küçük resminden seçilen tema.
26
+ *
27
+ * "Light · Dark · System" were three words, and a theme is not chosen with
28
+ * words: the person wants to see the result. "System" shows both halves in one
29
+ * frame, which is exactly what it means.
30
+ * TR: "Açık · Koyu · Sistem" üç kelimeydi, ve bir tema kelimeyle seçilmiyor:
31
+ * kullanıcı sonucu görmek istiyor. "Sistem" iki yarımı tek karede gösteriyor,
32
+ * anlamı tam olarak bu.
33
+ */
34
+ export declare function ThemeCards({ value, onChange, labels, className, }: {
35
+ value: ThemeChoice;
36
+ onChange: (next: ThemeChoice) => void;
37
+ /** The three names and the group's accessible name. TR: Üç ad ve kümenin erişilebilir adı. */
38
+ labels: {
39
+ light: string;
40
+ dark: string;
41
+ system: string;
42
+ group: string;
43
+ };
44
+ className?: string;
45
+ }): import("react").JSX.Element;
46
+ /**
47
+ * One image with a preview, a replace and a remove.
48
+ * TR: Önizlemesi, değiştirmesi ve kaldırması olan tek bir görsel.
49
+ *
50
+ * NOT `FileUpload`: that one is a GALLERY (multi select, reorder arrows, a
51
+ * "cover" badge). Right for product photos, wrong for a single logo, where it
52
+ * offers reorder arrows with nothing to reorder.
53
+ * TR: `FileUpload` DEĞİL: o bir GALERİ (çoklu seçim, sıralama okları, "kapak"
54
+ * rozeti). Ürün fotoğrafları için doğru, tek bir logo için yanlış.
55
+ */
56
+ export declare function ImageField({ value, onChange, maxEdge, labels, preview, extra, className, }: {
57
+ value: string | null;
58
+ onChange: (next: string | null) => void;
59
+ /** Longest edge of the stored image, in pixels. TR: Saklanan görselin en uzun kenarı, piksel. */
60
+ maxEdge: number;
61
+ labels: {
62
+ /** "Logo", "Mark": used inside the buttons. TR: "Logo", "Amblem": düğmelerin içinde geçiyor. */
63
+ name: string;
64
+ upload: string;
65
+ replace: string;
66
+ remove: string;
67
+ empty: string;
68
+ errorType: string;
69
+ errorSize: string;
70
+ errorUnreadable: string;
71
+ };
72
+ /** The caller draws the preview: a logo is wide, a mark is square. TR: Önizlemeyi çağıran çiziyor: logo geniş, amblem kare. */
73
+ preview: (src: string) => ReactNode;
74
+ /** An extra control beside the buttons. TR: Düğmelerin yanına giren ek kontrol. */
75
+ extra?: ReactNode;
76
+ className?: string;
77
+ }): import("react").JSX.Element;
78
+ //# sourceMappingURL=appearance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance.d.ts","sourceRoot":"","sources":["../../src/components/appearance.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAchE,MAAM,MAAM,YAAY,GAAG;IACzB,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,kHAAkH;IAClH,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAAW,EACX,MAAa,EACb,SAAS,GACV,EAAE;IACD,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,uGAAuG;IACvG,WAAW,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAqCA;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAoBtD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,GACV,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACtC,8FAA8F;IAC9F,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA6BA;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,OAAO,EACP,MAAM,EACN,OAAO,EACP,KAAK,EACL,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACxC,iGAAiG;IACjG,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE;QACN,gGAAgG;QAChG,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,+HAA+H;IAC/H,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC;IACpC,mFAAmF;IACnF,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAoDA"}
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useRef, useState } from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { IMAGE_ACCEPT, prepareImage } from "../lib/image.js";
6
+ import { Alert } from "./primitives.js";
7
+ import { Button } from "./button.js";
8
+ /**
9
+ * Brand colour chosen from a few squares, or written by hand.
10
+ * TR: Birkaç kareden seçilen ya da elle yazılan marka rengi.
11
+ */
12
+ export function ColorSwatches({ options, value, onChange, customLabel, custom = true, className, }) {
13
+ const secili = (hex) => hex.toLowerCase() === value.toLowerCase();
14
+ const hazirMi = options.some((o) => secili(o.hex));
15
+ return (_jsxs("span", { className: cn("flex flex-wrap items-center gap-3", className), children: [options.map((o) => (_jsx("button", { type: "button", className: "tamga-swatch", "data-selected": secili(o.hex), "aria-label": o.label, title: o.label, onClick: () => onChange(o.hex), children: _jsx("span", { "aria-hidden": true, style: { background: o.hex } }) }, o.hex))), custom ? (_jsxs("label", { className: "tamga-swatch", "data-selected": !hazirMi, title: customLabel, children: [_jsx("span", { className: "sr-only", children: customLabel }), _jsx("input", { type: "color", value: hazirMi ? options[0].hex : value, onChange: (e) => onChange(e.target.value), className: "tamga-color-input", "aria-label": customLabel })] })) : null] }));
16
+ }
17
+ function Yarim({ theme }) {
18
+ return (_jsxs("span", { className: "tamga-theme-half", "data-theme": theme, "aria-hidden": true, children: [_jsxs("span", { className: "tamga-theme-rail", children: [_jsx("span", { className: "tamga-theme-dot", "data-accent": "true" }), _jsx("span", { className: "tamga-theme-dot" }), _jsx("span", { className: "tamga-theme-dot" })] }), _jsxs("span", { className: "tamga-theme-body", children: [_jsx("span", { className: "tamga-theme-line", style: { width: "70%" } }), _jsx("span", { className: "tamga-theme-line", style: { width: "45%" } }), _jsx("span", { className: "tamga-theme-line", "data-accent": "true", style: { width: "30%" } }), _jsx("span", { className: "tamga-theme-line", style: { width: "60%" } })] })] }));
19
+ }
20
+ /**
21
+ * Theme picked from three little pictures of the panel.
22
+ * TR: Panelin üç küçük resminden seçilen tema.
23
+ *
24
+ * "Light · Dark · System" were three words, and a theme is not chosen with
25
+ * words: the person wants to see the result. "System" shows both halves in one
26
+ * frame, which is exactly what it means.
27
+ * TR: "Açık · Koyu · Sistem" üç kelimeydi, ve bir tema kelimeyle seçilmiyor:
28
+ * kullanıcı sonucu görmek istiyor. "Sistem" iki yarımı tek karede gösteriyor,
29
+ * anlamı tam olarak bu.
30
+ */
31
+ export function ThemeCards({ value, onChange, labels, className, }) {
32
+ const secenekler = [
33
+ { deger: "light", ad: labels.light },
34
+ { deger: "dark", ad: labels.dark },
35
+ { deger: "system", ad: labels.system },
36
+ ];
37
+ return (_jsx("div", { role: "radiogroup", "aria-label": labels.group, className: cn("flex flex-wrap gap-3", className), children: secenekler.map((s) => (_jsxs("button", { type: "button", role: "radio", "aria-checked": value === s.deger, tabIndex: value === s.deger ? 0 : -1, "data-selected": value === s.deger, className: "tamga-theme-card", onClick: () => onChange(s.deger), children: [_jsxs("span", { className: "tamga-theme-box", children: [s.deger === "dark" ? _jsx(Yarim, { theme: "dark" }) : _jsx(Yarim, { theme: "light" }), s.deger === "system" ? _jsx(Yarim, { theme: "dark" }) : null] }), _jsx("span", { className: "tamga-theme-name", children: s.ad })] }, s.deger))) }));
38
+ }
39
+ /**
40
+ * One image with a preview, a replace and a remove.
41
+ * TR: Önizlemesi, değiştirmesi ve kaldırması olan tek bir görsel.
42
+ *
43
+ * NOT `FileUpload`: that one is a GALLERY (multi select, reorder arrows, a
44
+ * "cover" badge). Right for product photos, wrong for a single logo, where it
45
+ * offers reorder arrows with nothing to reorder.
46
+ * TR: `FileUpload` DEĞİL: o bir GALERİ (çoklu seçim, sıralama okları, "kapak"
47
+ * rozeti). Ürün fotoğrafları için doğru, tek bir logo için yanlış.
48
+ */
49
+ export function ImageField({ value, onChange, maxEdge, labels, preview, extra, className, }) {
50
+ const id = useId();
51
+ const girdi = useRef(null);
52
+ const [hata, setHata] = useState(null);
53
+ async function al(file) {
54
+ if (!file)
55
+ return;
56
+ setHata(null);
57
+ try {
58
+ onChange(await prepareImage(file, maxEdge));
59
+ }
60
+ catch (e) {
61
+ const kod = e instanceof Error ? e.message : "unreadable";
62
+ setHata(kod === "type" ? labels.errorType : kod === "size" ? labels.errorSize : labels.errorUnreadable);
63
+ }
64
+ finally {
65
+ /* GİRDİ SIFIRLANIYOR: aynı dosya ikinci kez seçilince `change` hiç
66
+ çıkmıyor, çünkü değer değişmiyor. Kullanıcı için bu "ikinci denemede
67
+ çalışmıyor" demek. */
68
+ if (girdi.current)
69
+ girdi.current.value = "";
70
+ }
71
+ }
72
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [_jsx("input", { ref: girdi, id: id, type: "file", accept: IMAGE_ACCEPT, className: "sr-only", 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": value ? undefined : "true", children: value ? preview(value) : labels.empty }), _jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", onClick: () => girdi.current?.click(), children: value ? labels.replace : labels.upload }), extra, value ? (_jsx(Button, { type: "button", variant: "ghost", onClick: () => onChange(null), children: labels.remove })) : null] })] }), hata ? _jsx(Alert, { state: "danger", title: hata }) : null] }));
73
+ }
@@ -1,11 +1,11 @@
1
- import type { ReactNode } from "react";
1
+ import type { ComponentType, ReactNode } from "react";
2
2
  /**
3
3
  * Kendi dosyasında, çünkü YENİ BİR KONTROL.
4
4
  *
5
5
  * Gerekçe: docs/gerekce/05-yuzey-ve-kabuk.md
6
6
  */
7
7
  /** İkon raylı gezinme öğesi. */
8
- export declare function RailLink({ label, active, href, onClick, className, children, showLabel, }: {
8
+ export declare function RailLink({ label, active, href, onClick, className, children, showLabel, linkComponent: Link, }: {
9
9
  label: string;
10
10
  active?: boolean;
11
11
  href?: string;
@@ -20,5 +20,18 @@ export declare function RailLink({ label, active, href, onClick, className, chil
20
20
  * çubuğunda `true`.
21
21
  */
22
22
  showLabel?: boolean;
23
+ /**
24
+ * The router's link, so the rail does not force a full page load. Without it this component
25
+ * can only draw a plain `<a>`, and a shell that needs client routing ends up writing its own
26
+ * rail link instead of using this one. TR: Yönlendiricinin bağlantısı, ray tam sayfa
27
+ * yüklemeye zorlamasın diye. Olmadığında bu bileşen yalnız düz bir `<a>` çizebiliyor, ve
28
+ * istemci yönlendirmesi isteyen bir kabuk bunu kullanmak yerine kendi ray bağlantısını
29
+ * yazıyor.
30
+ */
31
+ linkComponent?: ComponentType<{
32
+ href: string;
33
+ children?: ReactNode;
34
+ [k: string]: unknown;
35
+ }>;
23
36
  }): import("react").JSX.Element;
24
37
  //# sourceMappingURL=rail-link.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rail-link.d.ts","sourceRoot":"","sources":["../../src/components/rail-link.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC;;;;GAIG;AAEH,gCAAgC;AAChC,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,MAAc,EACd,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,SAAiB,GAClB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,+BAwBA"}
1
+ {"version":3,"file":"rail-link.d.ts","sourceRoot":"","sources":["../../src/components/rail-link.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAItD;;;;GAIG;AAEH,gCAAgC;AAChC,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,MAAc,EACd,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,SAAiB,EACjB,aAAa,EAAE,IAAI,GACpB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;CAC7F,+BA2CA"}
@@ -1,24 +1,26 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { cn } from "../lib/cn.js";
4
+ import { Tooltip } from "./overlay.js";
4
5
  /**
5
6
  * Kendi dosyasında, çünkü YENİ BİR KONTROL.
6
7
  *
7
8
  * Gerekçe: docs/gerekce/05-yuzey-ve-kabuk.md
8
9
  */
9
10
  /** İkon raylı gezinme öğesi. */
10
- export function RailLink({ label, active = false, href, onClick, className, children, showLabel = false, }) {
11
+ export function RailLink({ label, active = false, href, onClick, className, children, showLabel = false, linkComponent: Link, }) {
11
12
  const shared = {
12
13
  className: cn("tamga-rail-link", showLabel && "tamga-rail-link-wide", className),
13
14
  "data-active": active,
14
15
  "aria-label": label,
15
- /* Etiket görünürken `title` yoktur: görünen bir metnin üstünde beliren
16
- ipucu aynı şeyi ikinci kez söyler ve imleci geciktirir. */
17
- title: showLabel ? undefined : label,
18
16
  "aria-current": active ? "page" : undefined,
19
17
  };
20
- if (href) {
21
- return (_jsxs("a", { href: href, ...shared, children: [children, showLabel && _jsx("span", { className: "truncate", children: label })] }));
22
- }
23
- return (_jsxs("button", { type: "button", onClick: onClick, ...shared, children: [children, showLabel && _jsx("span", { className: "truncate", children: label })] }));
18
+ const govde = (_jsxs(_Fragment, { children: [children, showLabel && _jsx("span", { className: "min-w-0 truncate", children: label })] }));
19
+ const kontrol = href ? (Link ? (_jsx(Link, { href: href, ...shared, children: govde })) : (_jsx("a", { href: href, ...shared, children: govde }))) : (_jsx("button", { type: "button", onClick: onClick, ...shared, children: govde }));
20
+ /* İPUCU BİLEŞENİN KENDİ İŞİ, ÇAĞIRANIN DEĞİL.
21
+ Burada `title={label}` vardı: tarayıcının gecikmeli gri balonu, kitin her
22
+ yerdeki ipucundan başka türlü görünüyor. Çağıranlar bu yüzden bir de elle
23
+ `Tooltip` sarıyordu ve ikisi üst üste biniyordu. Etiket görünürken ipucu
24
+ yok: görünen bir metnin üstünde beliren ipucu aynı şeyi ikinci kez söyler. */
25
+ return showLabel ? (kontrol) : (_jsx(Tooltip, { label: label, placement: "right", children: kontrol }));
24
26
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * LOGODAN AMBLEM SEÇME — otomatik kesme DEĞİL.
3
+ *
4
+ * Sorulan soru şuydu: yüklenen logoyu ayırıp amblemi kendiliğinden çıkaramaz
5
+ * mıyız? Çıkaramayız, ve denememesi gerekiyor:
6
+ *
7
+ * · Dosyada "amblem" diye işaretli bir şey yok. SVG'de bazen bir grup id'si
8
+ * olur ama bu tasarımcının keyfine bağlı; PNG'de hiç yok.
9
+ * · Konum sabit değil: amblem solda, üstte, sağda olabilir, yazının içine
10
+ * gömülü olabilir, ya da hiç olmayabilir (yalnız kelime-logo).
11
+ * · Hata SESSİZ ve KALICI olur. Yanlış kesim patlamaz; yarım bir harf
12
+ * panelin her sayfasının sol üstünde durur ve kimse bunun otomatik
13
+ * kesildiğini bilmez.
14
+ *
15
+ * Bu yüzden kesimi İNSAN yapıyor: kare bir çerçeve, sürüklenip boyutlanıyor.
16
+ * Sonuç tahmin değil karar.
17
+ *
18
+ * ÇERÇEVE ORANLA TUTULUYOR (0–1), piksel ile değil: önizleme ekrandan ekrana
19
+ * farklı ölçekte çiziliyor, oran her ölçekte aynı yeri gösteriyor. Ekrana
20
+ * çizilirken piksele dönüyor ve dönüşüm TEK YERDE (`kenarPx`): kesim
21
+ * matematiği ile çerçevenin CSS'i ayrı ayrı hesaplansaydı, kullanıcının
22
+ * gördüğü kare ile kesilen kare farklı olurdu — ve fark sessiz olurdu.
23
+ */
24
+ export declare function SquarePicker({ open, source, onClose, onPick, maxEdge, labels, }: {
25
+ open: boolean;
26
+ /** The image the square is cut out of. TR: Üzerinden kesilecek görsel. */
27
+ source: string;
28
+ onClose: () => void;
29
+ onPick: (square: string) => void;
30
+ /** Edge of the produced square, in pixels. TR: Üretilen karenin kenarı, piksel. */
31
+ maxEdge?: number;
32
+ labels: {
33
+ title: string;
34
+ hint: string;
35
+ size: string;
36
+ cancel: string;
37
+ confirm: string;
38
+ close: string;
39
+ };
40
+ }): import("react").JSX.Element;
41
+ //# sourceMappingURL=square-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"square-picker.d.ts","sourceRoot":"","sources":["../../src/components/square-picker.tsx"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,MAAM,EACN,OAAO,EACP,MAAM,EACN,OAAa,EACb,MAAM,GACP,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH,+BAmHA"}
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import { Button } from "./button.js";
5
+ import { Dialog } from "./overlay.js";
6
+ import { Slider } from "./slider.js";
7
+ import { Label } from "./surface.js";
8
+ import { cropSquare } from "../lib/image.js";
9
+ /**
10
+ * LOGODAN AMBLEM SEÇME — otomatik kesme DEĞİL.
11
+ *
12
+ * Sorulan soru şuydu: yüklenen logoyu ayırıp amblemi kendiliğinden çıkaramaz
13
+ * mıyız? Çıkaramayız, ve denememesi gerekiyor:
14
+ *
15
+ * · Dosyada "amblem" diye işaretli bir şey yok. SVG'de bazen bir grup id'si
16
+ * olur ama bu tasarımcının keyfine bağlı; PNG'de hiç yok.
17
+ * · Konum sabit değil: amblem solda, üstte, sağda olabilir, yazının içine
18
+ * gömülü olabilir, ya da hiç olmayabilir (yalnız kelime-logo).
19
+ * · Hata SESSİZ ve KALICI olur. Yanlış kesim patlamaz; yarım bir harf
20
+ * panelin her sayfasının sol üstünde durur ve kimse bunun otomatik
21
+ * kesildiğini bilmez.
22
+ *
23
+ * Bu yüzden kesimi İNSAN yapıyor: kare bir çerçeve, sürüklenip boyutlanıyor.
24
+ * Sonuç tahmin değil karar.
25
+ *
26
+ * ÇERÇEVE ORANLA TUTULUYOR (0–1), piksel ile değil: önizleme ekrandan ekrana
27
+ * farklı ölçekte çiziliyor, oran her ölçekte aynı yeri gösteriyor. Ekrana
28
+ * çizilirken piksele dönüyor ve dönüşüm TEK YERDE (`kenarPx`): kesim
29
+ * matematiği ile çerçevenin CSS'i ayrı ayrı hesaplansaydı, kullanıcının
30
+ * gördüğü kare ile kesilen kare farklı olurdu — ve fark sessiz olurdu.
31
+ */
32
+ export function SquarePicker({ open, source, onClose, onPick, maxEdge = 256, labels, }) {
33
+ const sahne = useRef(null);
34
+ const [olcu, setOlcu] = useState({ en: 0, boy: 0 });
35
+ const [x, setX] = useState(0);
36
+ const [y, setY] = useState(0);
37
+ const [oran, setOran] = useState(0.6);
38
+ const [suruyor, setSuruyor] = useState(false);
39
+ /* Her açılışta baştan: bir önceki logonun çerçevesini yeni logoya taşımak,
40
+ kullanıcıya alakasız bir yeri seçtirmenin en kolay yolu. */
41
+ useEffect(() => {
42
+ if (!open)
43
+ return;
44
+ setX(0);
45
+ setY(0);
46
+ setOran(0.6);
47
+ }, [open, source]);
48
+ /* Sahnenin gerçek boyu ÖLÇÜLÜYOR, tahmin edilmiyor: görsel yüklendikçe ve
49
+ pencere daraldıkça değişiyor, ve çerçevenin pikseli ona bağlı. */
50
+ useLayoutEffect(() => {
51
+ const el = sahne.current;
52
+ if (!el)
53
+ return;
54
+ const olc = () => setOlcu({ en: el.clientWidth, boy: el.clientHeight });
55
+ olc();
56
+ const gozcu = new ResizeObserver(olc);
57
+ gozcu.observe(el);
58
+ return () => gozcu.disconnect();
59
+ }, [open, source]);
60
+ /* KARE, SAHNENİN KISA KENARINA GÖRE. Bir logo 350×65 ise kare en fazla o 65
61
+ pikselin kadarı olabilir; uzun kenara göre ölçseydik çerçeve görselin
62
+ dışına taşardı. Kesim fonksiyonu da aynı kuralı kullanıyor. */
63
+ const kenarPx = Math.min(olcu.en, olcu.boy) * oran;
64
+ const tasi = useCallback((olay) => {
65
+ const el = sahne.current;
66
+ if (!suruyor || !el)
67
+ return;
68
+ const k = el.getBoundingClientRect();
69
+ const nx = (olay.clientX - k.left - kenarPx / 2) / k.width;
70
+ const ny = (olay.clientY - k.top - kenarPx / 2) / k.height;
71
+ setX(Math.max(0, Math.min(1 - kenarPx / k.width, nx)));
72
+ setY(Math.max(0, Math.min(1 - kenarPx / k.height, ny)));
73
+ }, [suruyor, kenarPx]);
74
+ async function onayla() {
75
+ onPick(await cropSquare(source, { x, y, size: oran }, maxEdge));
76
+ onClose();
77
+ }
78
+ return (_jsx(Dialog, { open: open, onClose: onClose, title: labels.title, closeLabel: labels.close, footer: _jsxs(_Fragment, { children: [_jsx(Button, { type: "button", onClick: onClose, children: labels.cancel }), _jsx(Button, { variant: "primary", type: "button", onClick: onayla, children: labels.confirm })] }), children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Label, { children: labels.hint }), _jsx("div", { className: "rounded-[var(--radius-card)] border border-[var(--color-edge)] bg-[var(--color-shell)] p-4", children: _jsxs("div", { ref: sahne, onPointerDown: (e) => {
79
+ setSuruyor(true);
80
+ e.currentTarget.setPointerCapture(e.pointerId);
81
+ }, onPointerMove: tasi, onPointerUp: () => setSuruyor(false), className: "relative cursor-crosshair touch-none select-none overflow-hidden", children: [_jsx("img", { src: source, alt: "", className: "pointer-events-none block w-full", draggable: false }), _jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute border-2 border-[var(--color-accent-line)]", style: {
82
+ left: x * olcu.en,
83
+ top: y * olcu.boy,
84
+ width: kenarPx,
85
+ height: kenarPx,
86
+ /* Dışarısı karartılıyor: seçili kare kendi kenarlığından çok
87
+ çevresinin koyulmasıyla okunuyor. */
88
+ boxShadow: "0 0 0 9999px rgb(0 0 0 / 0.35)",
89
+ } })] }) }), _jsx(Slider, { label: labels.size, value: Math.round(oran * 100), onChange: (v) => setOran(v / 100), min: 15, max: 100, suffix: "%" })] }) }));
90
+ }
package/dist/index.d.ts CHANGED
@@ -41,6 +41,9 @@ export { LiveScope, useLiveClaim } from "./components/live-scope.js";
41
41
  export { Skeleton, SkeletonText, SkeletonRows, SkeletonKpi, SkeletonCard, SkeletonTable, SkeletonOptions, SkeletonPanel, SkeletonPageBand, } from "./components/skeleton.js";
42
42
  export { Surface, ScrollX, Rise, Swap, ListRow, CellActions, PageBand, Link, } from "./components/layout.js";
43
43
  export { ThemeToggle, LocaleSwitcher, LogoTile, AccountButton } from "./components/chrome.js";
44
+ export { ColorSwatches, ThemeCards, ImageField, type SwatchOption, type ThemeChoice, } from "./components/appearance.js";
45
+ export { SquarePicker } from "./components/square-picker.js";
46
+ export { prepareImage, cropSquare, IMAGE_ACCEPT, type ImageError } from "./lib/image.js";
44
47
  export { RailLink } from "./components/rail-link.js";
45
48
  export { Slider } from "./components/slider.js";
46
49
  export { Accordion, Collapsible } from "./components/disclosure.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -54,6 +54,9 @@ export { Skeleton, SkeletonText, SkeletonRows, SkeletonKpi, SkeletonCard, Skelet
54
54
  * Gerekçesi `components/layout.tsx`'in başında. */
55
55
  export { Surface, ScrollX, Rise, Swap, ListRow, CellActions, PageBand, Link, } from "./components/layout.js";
56
56
  export { ThemeToggle, LocaleSwitcher, LogoTile, AccountButton } from "./components/chrome.js";
57
+ export { ColorSwatches, ThemeCards, ImageField, } from "./components/appearance.js";
58
+ export { SquarePicker } from "./components/square-picker.js";
59
+ export { prepareImage, cropSquare, IMAGE_ACCEPT } from "./lib/image.js";
57
60
  export { RailLink } from "./components/rail-link.js";
58
61
  export { Slider } from "./components/slider.js";
59
62
  /* --- açılır bölümler --------------------------------------------------- */
package/dist/kit.css CHANGED
@@ -999,7 +999,7 @@
999
999
  min-width: 28px;
1000
1000
  height: 28px;
1001
1001
  padding: 0 6px;
1002
- border-radius: var(--radius-mark);
1002
+ border-radius: var(--radius-sm);
1003
1003
  color: var(--color-ink-soft);
1004
1004
  font-size: var(--text-caption);
1005
1005
  cursor: pointer;
@@ -1283,9 +1283,14 @@
1283
1283
  color: var(--color-accent-line);
1284
1284
  box-shadow: 2px 2px 0 0 var(--color-accent-line);
1285
1285
  }
1286
+ /* DOLGU `accent`, KENAR `accent-shadow` — düğmenin sözleşmesinin aynısı.
1287
+ Burada dolgu `accent-line` idi: o bir METİN token'ı, sayfaya karşı 4.5
1288
+ arayarak üretiliyor, yani yüzden hep daha koyu. Sonuç ekranda şuydu: biten
1289
+ adımlar koyu, aynı ekrandaki her düğme ve rozet açık. Üstündeki mürekkep de
1290
+ `accent`e göre ölçülüyordu, `accent-line`a göre değil. */
1286
1291
  .tamga-step-mark[data-state="done"] {
1287
- border-color: var(--color-accent-line);
1288
- background: var(--color-accent-line);
1292
+ border-color: var(--color-accent-shadow);
1293
+ background: var(--color-accent);
1289
1294
  color: var(--color-accent-ink);
1290
1295
  }
1291
1296
  .tamga-step-line {
@@ -1296,7 +1301,7 @@
1296
1301
  background: var(--color-edge);
1297
1302
  }
1298
1303
  .tamga-step-line[data-done="true"] {
1299
- background: var(--color-accent-line);
1304
+ background: var(--color-accent);
1300
1305
  }
1301
1306
 
1302
1307
  .tamga-scrim {
@@ -1812,6 +1817,209 @@
1812
1817
  animation: tamga-sheet-end var(--duration-base) var(--ease-standard);
1813
1818
  }
1814
1819
 
1820
+ /* ------------------------------------------------------------------ *
1821
+ * GÖRÜNÜM AYARLARININ PARÇALARI.
1822
+ *
1823
+ * Üçü de bir üründe doğdu ve kite AYNI GEREKÇEYLE taşındı: her panelin bir
1824
+ * görünüm ayarı oluyor, ve kit onu vermediğinde her ürün kendi hex kutusunu
1825
+ * çiziyor. Kiti kuran bir ekip "renk seçimi" deyince altı hazır
1826
+ * kutuyu değil, çıplak bir metin girdisi elde ediyordu.
1827
+ * ------------------------------------------------------------------ */
1828
+
1829
+ /* Marka rengi kutusu. Seçili olan kitin fiziğiyle işaretli: 1px kenar artı
1830
+ 2px sert kaydırma. Kutunun İÇİNE çentik konmuyor, çentiğin kontrastı
1831
+ seçilen renge göre değişiyor ve açık bir renkte kayboluyor. */
1832
+ .tamga-swatch {
1833
+ display: inline-flex;
1834
+ width: var(--control);
1835
+ height: var(--control);
1836
+ align-items: center;
1837
+ justify-content: center;
1838
+ border: 1px solid var(--color-edge);
1839
+ border-radius: var(--radius-ctl);
1840
+ background: var(--color-shell);
1841
+ cursor: pointer;
1842
+ transition:
1843
+ transform var(--duration-press) var(--ease-standard),
1844
+ box-shadow var(--duration-press) var(--ease-standard);
1845
+ }
1846
+ .tamga-swatch > span {
1847
+ width: 20px;
1848
+ height: 20px;
1849
+ border-radius: var(--radius-mark);
1850
+ }
1851
+ .tamga-swatch:hover {
1852
+ transform: translate(-1px, -1px);
1853
+ box-shadow: 3px 3px 0 var(--color-edge);
1854
+ }
1855
+ .tamga-swatch[data-selected="true"] {
1856
+ border-color: var(--color-accent-line);
1857
+ box-shadow: 2px 2px 0 var(--color-accent-line);
1858
+ }
1859
+ .tamga-swatch:active {
1860
+ transform: translate(2px, 2px);
1861
+ box-shadow: 0 0 0 var(--color-edge);
1862
+ }
1863
+
1864
+ /* Tarayıcının kendi renk kutusu, kitin kenarlığıyla. */
1865
+ .tamga-color-input {
1866
+ width: 36px;
1867
+ height: 36px;
1868
+ padding: 2px;
1869
+ border: 1px solid var(--color-edge);
1870
+ border-radius: var(--radius-ctl);
1871
+ background: var(--color-shell);
1872
+ cursor: pointer;
1873
+ }
1874
+
1875
+ /* ------------------------------------------------------------------ *
1876
+ * TEMA KARTI — panelin minyatürü.
1877
+ *
1878
+ * "Açık · Koyu · Sistem" üç kelimeydi, ve bir tema seçimi kelimeyle
1879
+ * yapılmıyor: kullanıcı sonucu görmek istiyor. Her seçenek panelin
1880
+ * minyatürünü çiziyor (ray, gövde, satırlar) ve "Sistem" ikisini bir karede
1881
+ * yan yana gösteriyor, çünkü anlamı tam olarak bu.
1882
+ *
1883
+ * MİNYATÜR SABİT RENKLERLE, ve kitin tek istisnası bu. Seçenekler o an
1884
+ * yürürlükteki temayı değil SEÇİLİRSE ne olacağını gösteriyor; token
1885
+ * kullanılsaydı üçü de aynı görünürdü. Tek token: marka vurgusu, çünkü
1886
+ * önizlemenin yarısı o.
1887
+ * ------------------------------------------------------------------ */
1888
+ .tamga-theme-card {
1889
+ display: flex;
1890
+ flex-direction: column;
1891
+ gap: 8px;
1892
+ padding: 0;
1893
+ border: 0;
1894
+ background: none;
1895
+ cursor: pointer;
1896
+ text-align: center;
1897
+ }
1898
+ .tamga-theme-box {
1899
+ position: relative;
1900
+ display: flex;
1901
+ overflow: hidden;
1902
+ width: 112px;
1903
+ height: 72px;
1904
+ border: 1px solid var(--color-edge);
1905
+ border-radius: var(--radius-card);
1906
+ box-shadow: 2px 2px 0 var(--color-edge);
1907
+ transition:
1908
+ transform var(--duration-press) var(--ease-standard),
1909
+ box-shadow var(--duration-press) var(--ease-standard),
1910
+ border-color var(--duration-press) var(--ease-standard);
1911
+ }
1912
+ .tamga-theme-card:hover .tamga-theme-box {
1913
+ transform: translate(-1px, -1px);
1914
+ box-shadow: 3px 3px 0 var(--color-edge);
1915
+ }
1916
+ .tamga-theme-card:active .tamga-theme-box {
1917
+ transform: translate(2px, 2px);
1918
+ box-shadow: 0 0 0 var(--color-edge);
1919
+ }
1920
+ .tamga-theme-card[data-selected="true"] .tamga-theme-box {
1921
+ border-color: var(--color-accent-line);
1922
+ box-shadow: 2px 2px 0 var(--color-accent-line);
1923
+ }
1924
+ .tamga-theme-name {
1925
+ font-size: var(--text-small);
1926
+ color: var(--color-ink-faint);
1927
+ }
1928
+ .tamga-theme-card[data-selected="true"] .tamga-theme-name {
1929
+ font-weight: 500;
1930
+ color: var(--color-ink);
1931
+ }
1932
+
1933
+ .tamga-theme-half {
1934
+ display: flex;
1935
+ min-width: 0;
1936
+ flex: 1;
1937
+ gap: 4px;
1938
+ padding: 6px;
1939
+ }
1940
+ .tamga-theme-half[data-theme="light"] { background: #f4f2ee; }
1941
+ .tamga-theme-half[data-theme="dark"] { background: #16161a; }
1942
+
1943
+ .tamga-theme-rail {
1944
+ display: flex;
1945
+ width: 12px;
1946
+ flex-shrink: 0;
1947
+ flex-direction: column;
1948
+ gap: 3px;
1949
+ border-radius: 3px;
1950
+ padding: 3px;
1951
+ }
1952
+ .tamga-theme-half[data-theme="light"] .tamga-theme-rail { background: #fcfcfc; }
1953
+ .tamga-theme-half[data-theme="dark"] .tamga-theme-rail { background: #232327; }
1954
+
1955
+ .tamga-theme-body {
1956
+ display: flex;
1957
+ min-width: 0;
1958
+ flex: 1;
1959
+ flex-direction: column;
1960
+ gap: 3px;
1961
+ border-radius: 3px;
1962
+ padding: 4px;
1963
+ }
1964
+ .tamga-theme-half[data-theme="light"] .tamga-theme-body { background: #fcfcfc; }
1965
+ .tamga-theme-half[data-theme="dark"] .tamga-theme-body { background: #232327; }
1966
+
1967
+ .tamga-theme-line {
1968
+ height: 3px;
1969
+ border-radius: 2px;
1970
+ }
1971
+ .tamga-theme-half[data-theme="light"] .tamga-theme-line { background: #d8d4cf; }
1972
+ .tamga-theme-half[data-theme="dark"] .tamga-theme-line { background: #3a3a42; }
1973
+ /* Vurgu tema kuralını YENMEK ZORUNDA: daha düşük özgüllükte yazıldığında
1974
+ `[data-theme] .tamga-theme-line` onu eziyor ve marka rengi önizlemede hiç
1975
+ görünmüyor. */
1976
+ .tamga-theme-half .tamga-theme-line[data-accent="true"] {
1977
+ background: var(--color-accent);
1978
+ }
1979
+
1980
+ .tamga-theme-dot {
1981
+ height: 5px;
1982
+ width: 5px;
1983
+ border-radius: 2px;
1984
+ }
1985
+ .tamga-theme-half[data-theme="light"] .tamga-theme-dot { background: #d8d4cf; }
1986
+ .tamga-theme-half[data-theme="dark"] .tamga-theme-dot { background: #3a3a42; }
1987
+ .tamga-theme-half .tamga-theme-dot[data-accent="true"] {
1988
+ background: var(--color-accent);
1989
+ }
1990
+
1991
+ /* Yüklenen görselin durduğu kutu. Çıplak duran bir logo "buraya yüklendi"
1992
+ demiyor, "burada unutulmuş" diyor. Yükseklik sabit: logo geniş, amblem
1993
+ kare, ve ikisi farklı yükseklikte kutularda dursaydı bloklar hizasız
1994
+ görünürdü. */
1995
+ .tamga-image-field {
1996
+ display: flex;
1997
+ align-items: center;
1998
+ justify-content: center;
1999
+ height: 72px;
2000
+ min-width: 96px;
2001
+ padding: 10px 14px;
2002
+ border: 1px solid var(--color-edge);
2003
+ border-radius: var(--radius-card);
2004
+ background: var(--color-shell);
2005
+ box-shadow: 2px 2px 0 var(--color-edge);
2006
+ }
2007
+ /* Boş hâl: kesik çizgi "burası dolabilir" diyor, düz kenar "burası boş
2008
+ kaldı" diyor. */
2009
+ .tamga-image-field[data-empty] {
2010
+ border-style: dashed;
2011
+ box-shadow: none;
2012
+ color: var(--color-ink-faint);
2013
+ font-size: var(--text-small);
2014
+ }
2015
+
2016
+ /* Ayar bölümü, sol kenarında marka rengi. `SettingsPanel`in `accent`
2017
+ varyantı: bir görünüm ekranında bloklar birbirinden ayrılmalı ve ayıran
2018
+ şey rengin kendisi olmalı, çünkü ekranın konusu o. */
2019
+ .tamga-settings-panel-accent {
2020
+ border-left: 4px solid var(--color-accent);
2021
+ }
2022
+
1815
2023
  .tamga-label {
1816
2024
  font-size: var(--text-caption);
1817
2025
  font-weight: 500;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Turning an uploaded file into something a panel can carry.
3
+ *
4
+ * NO LIBRARY, and none is needed: a `<canvas>` and `drawImage`.
5
+ *
6
+ * A DATA URI, NOT AN OBJECT URL. `URL.createObjectURL` dies with the tab, so a
7
+ * logo chosen today would be gone tomorrow. A data URI survives in storage and
8
+ * goes straight into `<img src>`. When a server arrives this file becomes an
9
+ * upload call and nothing else changes: screens already hold a STRING and do
10
+ * not care whether it is a data URI or an `https://` address.
11
+ *
12
+ * WHY IT IS RESIZED. `localStorage` holds about 5 MB per browser and a phone
13
+ * photo alone is 4 MB. Writing one unresized throws `QuotaExceeded`, and the
14
+ * thing that gets lost may not be the logo but whatever else was being saved
15
+ * at that moment.
16
+ *
17
+ * THE RATIO IS KEPT, NOTHING IS CROPPED. Cropping to fit a square is the most
18
+ * common and most silent mistake: the logo looks fine and its edge is gone.
19
+ * The long side is pulled to the limit, the short side follows.
20
+ *
21
+ * PNG, NOT JPEG. Logos are flat and transparent; JPEG turns transparency into
22
+ * solid black and rings the edges.
23
+ */
24
+ export type ImageError = "type" | "size" | "unreadable";
25
+ export declare const IMAGE_ACCEPT = "image/png,image/jpeg,image/svg+xml,image/webp";
26
+ /**
27
+ * Resizes so the longest side is `maxEdge` pixels and returns a data URI.
28
+ *
29
+ * SVG PASSES THROUGH UNTOUCHED. Drawing a vector onto a canvas and writing PNG
30
+ * stops it being scalable: a logo baked at 512px is blurry on a 4K screen. SVG
31
+ * is already small and already sharp at every size.
32
+ */
33
+ export declare function prepareImage(file: File, maxEdge: number): Promise<string>;
34
+ /**
35
+ * Cuts a SQUARE piece out of an image: for a mark or a profile photo.
36
+ *
37
+ * `area` is given in RATIOS (0 to 1), not in the image's own pixels, because
38
+ * the picker runs on a preview whose scale changes from screen to screen. A
39
+ * ratio points at the same place at every scale.
40
+ */
41
+ export declare function cropSquare(source: string, area: {
42
+ x: number;
43
+ y: number;
44
+ size: number;
45
+ }, maxEdge: number): Promise<string>;
46
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/lib/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,CAAC;AAKxD,eAAO,MAAM,YAAY,kDAAkD,CAAC;AAE5E;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoB/E;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAcjB"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Turning an uploaded file into something a panel can carry.
3
+ *
4
+ * NO LIBRARY, and none is needed: a `<canvas>` and `drawImage`.
5
+ *
6
+ * A DATA URI, NOT AN OBJECT URL. `URL.createObjectURL` dies with the tab, so a
7
+ * logo chosen today would be gone tomorrow. A data URI survives in storage and
8
+ * goes straight into `<img src>`. When a server arrives this file becomes an
9
+ * upload call and nothing else changes: screens already hold a STRING and do
10
+ * not care whether it is a data URI or an `https://` address.
11
+ *
12
+ * WHY IT IS RESIZED. `localStorage` holds about 5 MB per browser and a phone
13
+ * photo alone is 4 MB. Writing one unresized throws `QuotaExceeded`, and the
14
+ * thing that gets lost may not be the logo but whatever else was being saved
15
+ * at that moment.
16
+ *
17
+ * THE RATIO IS KEPT, NOTHING IS CROPPED. Cropping to fit a square is the most
18
+ * common and most silent mistake: the logo looks fine and its edge is gone.
19
+ * The long side is pulled to the limit, the short side follows.
20
+ *
21
+ * PNG, NOT JPEG. Logos are flat and transparent; JPEG turns transparency into
22
+ * solid black and rings the edges.
23
+ */
24
+ /** Raw file limit, before resizing. Over 8 MB is not a logo, it is an accident. */
25
+ const RAW_LIMIT = 8 * 1024 * 1024;
26
+ export const IMAGE_ACCEPT = "image/png,image/jpeg,image/svg+xml,image/webp";
27
+ /**
28
+ * Resizes so the longest side is `maxEdge` pixels and returns a data URI.
29
+ *
30
+ * SVG PASSES THROUGH UNTOUCHED. Drawing a vector onto a canvas and writing PNG
31
+ * stops it being scalable: a logo baked at 512px is blurry on a 4K screen. SVG
32
+ * is already small and already sharp at every size.
33
+ */
34
+ export async function prepareImage(file, maxEdge) {
35
+ if (!file.type.startsWith("image/"))
36
+ throw new Error("type");
37
+ if (file.size > RAW_LIMIT)
38
+ throw new Error("size");
39
+ const raw = await readFile(file);
40
+ if (file.type === "image/svg+xml")
41
+ return raw;
42
+ const image = await loadImage(raw);
43
+ const scale = Math.min(1, maxEdge / Math.max(image.width, image.height));
44
+ if (scale >= 1)
45
+ return raw;
46
+ const canvas = document.createElement("canvas");
47
+ canvas.width = Math.round(image.width * scale);
48
+ canvas.height = Math.round(image.height * scale);
49
+ const ctx = canvas.getContext("2d");
50
+ if (!ctx)
51
+ return raw;
52
+ /* The browser's own smoothing: better than a hand-written resampler, and free. */
53
+ ctx.imageSmoothingQuality = "high";
54
+ ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
55
+ return canvas.toDataURL("image/png");
56
+ }
57
+ /**
58
+ * Cuts a SQUARE piece out of an image: for a mark or a profile photo.
59
+ *
60
+ * `area` is given in RATIOS (0 to 1), not in the image's own pixels, because
61
+ * the picker runs on a preview whose scale changes from screen to screen. A
62
+ * ratio points at the same place at every scale.
63
+ */
64
+ export async function cropSquare(source, area, maxEdge) {
65
+ const image = await loadImage(source);
66
+ /* The square is measured against the SHORT side: a 350x65 logo can hold a
67
+ square of at most 65px. Measured against the long side the frame would
68
+ fall outside the image. */
69
+ const edge = Math.min(image.width, image.height) * area.size;
70
+ const canvas = document.createElement("canvas");
71
+ canvas.width = maxEdge;
72
+ canvas.height = maxEdge;
73
+ const ctx = canvas.getContext("2d");
74
+ if (!ctx)
75
+ return source;
76
+ ctx.imageSmoothingQuality = "high";
77
+ ctx.drawImage(image, area.x * image.width, area.y * image.height, edge, edge, 0, 0, maxEdge, maxEdge);
78
+ return canvas.toDataURL("image/png");
79
+ }
80
+ function readFile(file) {
81
+ return new Promise((resolve, reject) => {
82
+ const reader = new FileReader();
83
+ reader.onload = () => resolve(String(reader.result));
84
+ reader.onerror = () => reject(new Error("unreadable"));
85
+ reader.readAsDataURL(file);
86
+ });
87
+ }
88
+ function loadImage(src) {
89
+ return new Promise((resolve, reject) => {
90
+ const img = new Image();
91
+ img.onload = () => resolve(img);
92
+ img.onerror = () => reject(new Error("unreadable"));
93
+ img.src = src;
94
+ });
95
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../src/patterns/app-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,wKAAwK;IACxK,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,wJAAwJ;IACxJ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzB,mGAAmG;IACnG,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,uIAAuI;IACvI,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,GAAG,EACH,UAAU,EACV,IAAe,EACf,MAAM,EACN,UAAU,EACV,aAAa,EAAE,IAAgB,EAC/B,MAAM,EACN,QAAQ,GACT,EAAE,aAAa,+BAkFf"}
1
+ {"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../src/patterns/app-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,wKAAwK;IACxK,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,wJAAwJ;IACxJ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzB,mGAAmG;IACnG,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,uIAAuI;IACvI,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,GAAG,EACH,UAAU,EACV,IAAe,EACf,MAAM,EACN,UAAU,EACV,aAAa,EAAE,IAAgB,EAC/B,MAAM,EACN,QAAQ,GACT,EAAE,aAAa,+BAuEf"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Icon } from "../components/icon.js";
4
- import { Tooltip } from "../components/overlay.js";
4
+ import { RailLink } from "../components/rail-link.js";
5
5
  import { PlainLink } from "./shared.js";
6
6
  export function AppShell({ brand, nav, activePath, rail = "narrow", topbar, railFooter, linkComponent: Link = PlainLink, labels, children, }) {
7
7
  /* Bir giriş kendi alt ağacına sahip: `/urunler`, `/urunler/42/fotograflar`
@@ -9,15 +9,5 @@ export function AppShell({ brand, nav, activePath, rail = "narrow", topbar, rail
9
9
  altındaki her rotayı yutar. */
10
10
  const isCurrent = (href) => href === "/" ? activePath === href : activePath === href || activePath.startsWith(`${href}/`);
11
11
  const wide = rail === "wide";
12
- return (_jsxs("div", { className: "flex h-dvh w-full overflow-hidden bg-shell text-ink-soft", children: [_jsxs("aside", { className: `flex shrink-0 flex-col gap-2 bg-shell py-4 ${wide ? "w-60 px-3" : "w-20 items-center px-1"}`, "data-rail": rail, children: [brand ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-label": labels.home, className: `mb-2 flex h-10 shrink-0 items-center text-ink ${wide ? "px-2" : "w-10 justify-center"}`, children: brand }), _jsx("span", { className: `h-0 w-6 border-t border-line ${wide ? "ml-2" : ""}` })] })) : null, _jsx("nav", { "aria-label": labels.primaryNav, className: `tamga-rail-scroll flex w-full flex-1 flex-col gap-2 py-1 ${wide ? "" : "items-center"}`, children: nav.map((entry) => {
13
- const current = isCurrent(entry.href);
14
- const link = (_jsxs(Link, { href: entry.href, "aria-label": wide ? undefined : entry.label, "aria-current": current ? "page" : undefined, "data-active": current, "data-nav": entry.key, className: "tamga-rail-link", children: [_jsx(Icon, { icon: entry.icon, size: "base" }), wide ? _jsx("span", { className: "min-w-0 truncate", children: entry.label }) : null] }, entry.key));
15
- /* DAR RAYDA KİTİN İPUCU, `title` DEĞİL.
16
- Burada `title={entry.label}` vardı: tarayıcının kendi gecikmeli
17
- gri balonu. Kullanan kişi bunu "ipucu yok" diye okuyor, çünkü
18
- kitin her yerdeki ipucu başka türlü görünüyor ve hemen çıkıyor.
19
- Geniş rayda etiket zaten yazılı; orada ipucu aynı sözcüğü ikinci
20
- kez söylemek olurdu. */
21
- return wide ? (link) : (_jsx(Tooltip, { label: entry.label, placement: "right", children: link }, entry.key));
22
- }) }), railFooter ? _jsx("div", { className: "shrink-0", children: railFooter }) : null] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("header", { className: "tamga-gutter flex items-center gap-4 py-4", children: topbar }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden pr-4 pb-4", children: _jsx("main", { className: "tamga-surface h-full min-h-0 overflow-y-auto", children: children }) })] })] }));
12
+ return (_jsxs("div", { className: "flex h-dvh w-full overflow-hidden bg-shell text-ink-soft", children: [_jsxs("aside", { className: `flex shrink-0 flex-col gap-2 bg-shell py-4 ${wide ? "w-60 px-3" : "w-20 items-center px-1"}`, "data-rail": rail, children: [brand ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-label": labels.home, className: `mb-2 flex h-10 shrink-0 items-center text-ink ${wide ? "px-2" : "w-10 justify-center"}`, children: brand }), _jsx("span", { className: `h-0 w-6 border-t border-line ${wide ? "ml-2" : ""}` })] })) : null, _jsx("nav", { "aria-label": labels.primaryNav, className: `tamga-rail-scroll flex w-full flex-1 flex-col gap-2 py-1 ${wide ? "" : "items-center"}`, children: nav.map((entry) => (_jsx(RailLink, { href: entry.href, label: entry.label, active: isCurrent(entry.href), showLabel: wide, linkComponent: Link, "data-nav": entry.key, children: _jsx(Icon, { icon: entry.icon, size: "base" }) }, entry.key))) }), railFooter ? _jsx("div", { className: "shrink-0", children: railFooter }) : null] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("header", { className: "tamga-gutter flex items-center gap-4 py-4", children: topbar }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden pr-4 pb-4", children: _jsx("main", { className: "tamga-surface h-full min-h-0 overflow-y-auto", children: children }) })] })] }));
23
13
  }
@@ -0,0 +1,105 @@
1
+ import { type ReactNode } from "react";
2
+ import { type SwatchOption, type ThemeChoice } from "../components/appearance.js";
3
+ /**
4
+ * The Appearance screen: logo, mark, brand colour, theme, sidebar.
5
+ *
6
+ * WHY A WHOLE SCREEN AND NOT FIVE PARTS. Every panel grows an appearance
7
+ * setting, and the kit not giving one is why two products drew their own hex
8
+ * box. Handing over the parts would not have fixed it: each product arranges
9
+ * them differently and they drift apart again, which is the thing this layer
10
+ * exists to prevent (ADR-0004).
11
+ *
12
+ * IT STORES NOTHING. `value` comes in, `onChange` goes out, `onSave` belongs
13
+ * to the caller. Where the preference lives (session, account, browser) is a
14
+ * product decision and a template that learns it stops being a template.
15
+ *
16
+ * THE PALETTE IS NOT APPLIED HERE EITHER. This screen reports the chosen
17
+ * colour; turning it into tokens is `makePalette` plus `paletteVars`, and the
18
+ * product decides when that happens, because the product owns the root
19
+ * element.
20
+ */
21
+ export type Appearance = {
22
+ brand: string;
23
+ theme: ThemeChoice;
24
+ /** `free` means each user decides. TR: `free`, her kullanıcı kendi seçer demek. */
25
+ rail: "narrow" | "wide" | "free";
26
+ logo: string | null;
27
+ mark: string | null;
28
+ };
29
+ export type AppearanceLabels = {
30
+ title: string;
31
+ description: string;
32
+ logo: {
33
+ title: string;
34
+ description: string;
35
+ field: ImageLabels;
36
+ pickMark: string;
37
+ };
38
+ mark: {
39
+ title: string;
40
+ description: string;
41
+ field: ImageLabels;
42
+ fallbackNote: string;
43
+ };
44
+ brand: {
45
+ title: string;
46
+ description: string;
47
+ swatches: readonly SwatchOption[];
48
+ custom: string;
49
+ };
50
+ theme: {
51
+ title: string;
52
+ description: string;
53
+ light: string;
54
+ dark: string;
55
+ system: string;
56
+ group: string;
57
+ };
58
+ rail: {
59
+ title: string;
60
+ description: string;
61
+ narrow: string;
62
+ wide: string;
63
+ free: string;
64
+ group: string;
65
+ };
66
+ picker: {
67
+ title: string;
68
+ hint: string;
69
+ size: string;
70
+ cancel: string;
71
+ confirm: string;
72
+ close: string;
73
+ };
74
+ save: string;
75
+ saving: string;
76
+ reset: string;
77
+ clean: string;
78
+ dirty: string;
79
+ };
80
+ type ImageLabels = {
81
+ name: string;
82
+ upload: string;
83
+ replace: string;
84
+ remove: string;
85
+ empty: string;
86
+ errorType: string;
87
+ errorSize: string;
88
+ errorUnreadable: string;
89
+ };
90
+ export declare function AppearanceTemplate({ value, onChange, saved, onSave, onReset, saving, fallbackName, labels, extra, }: {
91
+ value: Appearance;
92
+ onChange: (next: Appearance) => void;
93
+ /** The stored version; "is anything unsaved" is answered by comparing with it. TR: Kayıtlı hâli; "kaydedilmemiş bir şey var mı" sorusu bununla cevaplanıyor. */
94
+ saved: Appearance;
95
+ onSave: () => void;
96
+ onReset: () => void;
97
+ saving?: boolean;
98
+ /** The name a tile is drawn from when there is no mark. TR: Amblem yokken karonun çizildiği ad. */
99
+ fallbackName: string;
100
+ labels: AppearanceLabels;
101
+ /** Extra panels the product adds, after the built-in ones. TR: Ürünün eklediği ek bölümler, hazır olanlardan sonra. */
102
+ extra?: ReactNode;
103
+ }): import("react").JSX.Element;
104
+ export {};
105
+ //# sourceMappingURL=appearance-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance-template.d.ts","sourceRoot":"","sources":["../../src/patterns/appearance-template.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { ColorSwatches, ImageField, ThemeCards } from "../components/appearance.js";
5
+ import { LogoTile } from "../components/chrome.js";
6
+ import { Segmented } from "../components/segmented.js";
7
+ import { SquarePicker } from "../components/square-picker.js";
8
+ import { Button } from "../components/button.js";
9
+ import { Label } from "../components/surface.js";
10
+ import { SettingsPanel } from "./settings-template.js";
11
+ export function AppearanceTemplate({ value, onChange, saved, onSave, onReset, saving = false, fallbackName, labels, extra, }) {
12
+ const [picking, setPicking] = useState(false);
13
+ const dirty = JSON.stringify(value) !== JSON.stringify(saved);
14
+ return (_jsxs("form", { className: "flex flex-col gap-5", onSubmit: (e) => {
15
+ e.preventDefault();
16
+ if (dirty && !saving)
17
+ onSave();
18
+ }, children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-display-sm font-semibold text-ink", children: labels.title }), _jsx("p", { className: "w-full text-control leading-relaxed text-ink-soft", children: labels.description })] }), _jsx(SettingsPanel, { accent: true, title: labels.logo.title, description: labels.logo.description, children: _jsx(ImageField, { value: value.logo, onChange: (v) => onChange({ ...value, logo: v }), maxEdge: 512, labels: labels.logo.field, preview: (src) => (_jsx("img", { src: src, alt: "", className: "max-h-full w-auto max-w-56 object-contain" })), extra: value.logo ? (_jsx(Button, { type: "button", onClick: () => setPicking(true), children: labels.logo.pickMark })) : undefined }) }), _jsxs(SettingsPanel, { accent: true, title: labels.mark.title, description: labels.mark.description, children: [_jsx(ImageField, { value: value.mark, onChange: (v) => onChange({ ...value, mark: v }), maxEdge: 256, labels: labels.mark.field, preview: (src) => (_jsx("img", { src: src, alt: "", className: "size-12 object-contain" })) }), !value.mark ? (_jsxs("span", { className: "mt-4 flex items-center gap-3", children: [_jsx(LogoTile, { name: fallbackName }), _jsx(Label, { children: labels.mark.fallbackNote })] })) : null] }), _jsx(SettingsPanel, { accent: true, title: labels.brand.title, description: labels.brand.description, children: _jsx(ColorSwatches, { options: labels.brand.swatches, value: value.brand, onChange: (hex) => onChange({ ...value, brand: hex }), customLabel: labels.brand.custom }) }), _jsx(SettingsPanel, { accent: true, title: labels.theme.title, description: labels.theme.description, children: _jsx(ThemeCards, { value: value.theme, onChange: (t) => onChange({ ...value, theme: t }), labels: labels.theme }) }), _jsx(SettingsPanel, { accent: true, title: labels.rail.title, description: labels.rail.description, children: _jsx(Segmented, { label: labels.rail.group, value: value.rail, onChange: (r) => onChange({ ...value, rail: r }), options: [
19
+ { value: "narrow", label: labels.rail.narrow },
20
+ { value: "wide", label: labels.rail.wide },
21
+ { value: "free", label: labels.rail.free },
22
+ ] }) }), extra, value.logo ? (_jsx(SquarePicker, { open: picking, source: value.logo, onClose: () => setPicking(false), onPick: (square) => onChange({ ...value, mark: square }), labels: labels.picker })) : null, _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-3", children: [_jsx(Button, { variant: "primary", type: "submit", disabled: !dirty, busy: saving, busyLabel: labels.saving, children: labels.save }), _jsx(Button, { type: "button", disabled: saving, onClick: onReset, children: labels.reset })] }), _jsx(Label, { children: dirty ? labels.dirty : labels.clean })] })] }));
23
+ }
@@ -15,4 +15,5 @@ export { AuthTemplate, AuthProviders } from "./auth-template.js";
15
15
  export { WizardTemplate, type WizardStep, type WizardTemplateProps } from "./wizard-template.js";
16
16
  export { PublicTemplate, type PublicState, type PublicTemplateProps } from "./public-template.js";
17
17
  export { AppShell, type NavEntry, type AppShellProps } from "./app-shell.js";
18
+ export { AppearanceTemplate, type Appearance, type AppearanceLabels, } from "./appearance-template.js";
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patterns/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EACL,SAAS,EACT,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC1F,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,qBAAqB,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,qBAAqB,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAClG,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patterns/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EACL,SAAS,EACT,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC1F,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,qBAAqB,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,qBAAqB,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAClG,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EACL,kBAAkB,EAClB,KAAK,UAAU,EACf,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC"}
@@ -19,3 +19,4 @@ export { AuthTemplate, AuthProviders } from "./auth-template.js";
19
19
  export { WizardTemplate } from "./wizard-template.js";
20
20
  export { PublicTemplate } from "./public-template.js";
21
21
  export { AppShell } from "./app-shell.js";
22
+ export { AppearanceTemplate, } from "./appearance-template.js";
@@ -40,9 +40,20 @@ export declare function SettingsTemplate({ sections, activeSection, title, subti
40
40
  * Bir değer üst bir düzeyden miras alınıyorsa, bunu bilmeden değiştirmek üç
41
41
  * ekran açıp "bu alan neden böyle yazıyor" diye aramak demek.
42
42
  */
43
- export declare function SettingsPanel({ title, description, inheritedFrom, children, }: {
43
+ export declare function SettingsPanel({ title, description, inheritedFrom, accent, children, }: {
44
44
  title: string;
45
45
  description?: string;
46
+ /**
47
+ * A brand-coloured edge down the left of the panel. TR: Panelin sol kenarında marka renginde
48
+ * bir şerit.
49
+ *
50
+ * For screens whose SUBJECT is the brand: on an appearance screen the blocks have to separate
51
+ * from one another, and the thing that separates them should be the colour itself. Elsewhere
52
+ * it would be decoration. TR: Konusu MARKANIN KENDİSİ olan ekranlar için: bir görünüm
53
+ * ekranında bloklar birbirinden ayrılmalı ve ayıran şey rengin kendisi olmalı. Başka yerde
54
+ * süs olurdu.
55
+ */
56
+ accent?: boolean;
46
57
  /** Like "inherited from the workspace": a full sentence written by the caller. TR: "çalışma alanından geliyor" gibi, çağıran tarafından yazılmış tam cümle. */
47
58
  inheritedFrom?: string;
48
59
  children: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"settings-template.d.ts","sourceRoot":"","sources":["../../src/patterns/settings-template.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAE1D,kCAAkC;AAClC,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;AAE/D,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gHAAgH;IAChH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;KACtC,GAAG,WAAW,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,aAAa,EACb,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAe,EACf,KAAK,EACL,aAAa,EAAE,IAAgB,EAC/B,MAAM,EACN,QAAQ,GACT,EAAE,qBAAqB,+BAsDvB;AA0BD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,WAAW,EACX,aAAa,EACb,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+JAA+J;IAC/J,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BA0BA"}
1
+ {"version":3,"file":"settings-template.d.ts","sourceRoot":"","sources":["../../src/patterns/settings-template.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAE1D,kCAAkC;AAClC,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;AAE/D,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gHAAgH;IAChH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;KACtC,GAAG,WAAW,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,aAAa,EACb,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAe,EACf,KAAK,EACL,aAAa,EAAE,IAAgB,EAC/B,MAAM,EACN,QAAQ,GACT,EAAE,qBAAqB,+BAsDvB;AA0BD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,WAAW,EACX,aAAa,EACb,MAAc,EACd,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,+JAA+J;IAC/J,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BA6BA"}
@@ -28,6 +28,6 @@ function ScopeMark({ scope, label }) {
28
28
  * Bir değer üst bir düzeyden miras alınıyorsa, bunu bilmeden değiştirmek üç
29
29
  * ekran açıp "bu alan neden böyle yazıyor" diye aramak demek.
30
30
  */
31
- export function SettingsPanel({ title, description, inheritedFrom, children, }) {
32
- return (_jsxs("section", { className: "tamga-card tamga-card-open tamga-gutter py-4", "data-panel": title, children: [_jsxs("div", { className: "flex flex-wrap items-baseline gap-2", children: [_jsx("h3", { className: "text-subhead font-semibold text-ink", children: title }), inheritedFrom ? (_jsx("span", { className: "text-caption text-ink-faint", "data-inherited-from": true, children: inheritedFrom })) : null] }), description ? (_jsx("p", { className: "mt-1 text-small leading-relaxed text-ink-soft", children: description })) : null, _jsx("div", { className: "mt-4", children: children })] }));
31
+ export function SettingsPanel({ title, description, inheritedFrom, accent = false, children, }) {
32
+ return (_jsxs("section", { className: `tamga-card tamga-card-open tamga-gutter py-4${accent ? " tamga-settings-panel-accent" : ""}`, "data-panel": title, children: [_jsxs("div", { className: "flex flex-wrap items-baseline gap-2", children: [_jsx("h3", { className: "text-subhead font-semibold text-ink", children: title }), inheritedFrom ? (_jsx("span", { className: "text-caption text-ink-faint", "data-inherited-from": true, children: inheritedFrom })) : null] }), description ? (_jsx("p", { className: "mt-1 text-small leading-relaxed text-ink-soft", children: description })) : null, _jsx("div", { className: "mt-4", children: children })] }));
33
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamga-ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Mantıksal's shared interface library: tokens, physics and components.",
5
5
  "license": "MIT",
6
6
  "type": "module",