tamga-ui 0.5.9 → 0.6.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 +23 -0
- package/dist/fonts.css +1 -1
- package/dist/kit.css +64 -1
- package/dist/patterns/app-shell.d.ts +20 -1
- package/dist/patterns/app-shell.d.ts.map +1 -1
- package/dist/patterns/app-shell.js +37 -3
- package/dist/styles.css +1 -1
- package/dist/theme.css +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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.6.0
|
|
9
|
+
|
|
10
|
+
> Sections fold; the one you are in never does.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`AppShell` section headers fold.** In the wide rail every section header is now a button:
|
|
15
|
+
click it and the section's entries slide shut; click again and they return. A customer with
|
|
16
|
+
eight sections asked for exactly this — the rail had become a wall. Three rules keep it honest:
|
|
17
|
+
the open page's section never folds (you can always read where you are), a route that lands
|
|
18
|
+
inside a folded section finds it open, and the narrow rail is untouched (nothing to fold when
|
|
19
|
+
there are no headers). New props: `collapsibleSections` (default `true`), `collapsedSections`
|
|
20
|
+
for a controlled rail, `onSectionToggle(section, open)`. Where the preference lives — browser,
|
|
21
|
+
account, device — is the product's call; the shell never writes storage.
|
|
22
|
+
|
|
23
|
+
### Breaking
|
|
24
|
+
|
|
25
|
+
- Section headers in the wide rail are `<button aria-expanded>` elements instead of `<p>`, and
|
|
26
|
+
each section's links now sit inside a `.tamga-rail-group-body` wrapper rather than directly
|
|
27
|
+
under `<nav>`. A selector written as `nav > .tamga-rail-link` or a test that read the header
|
|
28
|
+
as a paragraph needs updating; `data-nav` on every link is unchanged and is the supported hook.
|
|
29
|
+
Pass `collapsibleSections={false}` to keep plain headers (the wrapper stays either way).
|
|
30
|
+
|
|
8
31
|
## 0.5.9
|
|
9
32
|
|
|
10
33
|
> The hook lands on the root, in every branch.
|
package/dist/fonts.css
CHANGED
package/dist/kit.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! tamga-ui 0.
|
|
1
|
+
/*! tamga-ui 0.6.0 · MIT · https://tamga.org.tr */
|
|
2
2
|
:root {
|
|
3
3
|
|
|
4
4
|
--overlay-gap: 8px;
|
|
@@ -380,6 +380,69 @@
|
|
|
380
380
|
display: none;
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
.tamga-rail-section {
|
|
384
|
+
align-items: center;
|
|
385
|
+
background: transparent;
|
|
386
|
+
border: 0;
|
|
387
|
+
border-radius: var(--radius-ctl);
|
|
388
|
+
color: var(--color-ink-faint);
|
|
389
|
+
cursor: pointer;
|
|
390
|
+
display: flex;
|
|
391
|
+
font: inherit;
|
|
392
|
+
font-size: var(--text-caption);
|
|
393
|
+
font-weight: 700;
|
|
394
|
+
gap: 6px;
|
|
395
|
+
letter-spacing: var(--tracking-label);
|
|
396
|
+
padding: 4px 10px;
|
|
397
|
+
text-align: left;
|
|
398
|
+
text-transform: uppercase;
|
|
399
|
+
transition: background var(--duration-press) var(--ease-standard), color var(--duration-press) var(--ease-standard);
|
|
400
|
+
width: 100%;
|
|
401
|
+
}
|
|
402
|
+
.tamga-rail-section:hover {
|
|
403
|
+
background: var(--color-hover);
|
|
404
|
+
color: var(--color-ink);
|
|
405
|
+
}
|
|
406
|
+
.tamga-rail-section[data-locked="true"] {
|
|
407
|
+
cursor: default;
|
|
408
|
+
}
|
|
409
|
+
.tamga-rail-section[data-locked="true"]:hover {
|
|
410
|
+
background: transparent;
|
|
411
|
+
color: var(--color-ink-faint);
|
|
412
|
+
}
|
|
413
|
+
.tamga-rail-section-caret {
|
|
414
|
+
margin-left: auto;
|
|
415
|
+
transition: transform var(--duration-press) var(--ease-standard), opacity var(--duration-press) var(--ease-standard);
|
|
416
|
+
}
|
|
417
|
+
.tamga-rail-section[aria-expanded="false"] .tamga-rail-section-caret {
|
|
418
|
+
transform: rotate(-90deg);
|
|
419
|
+
}
|
|
420
|
+
.tamga-rail-section[data-locked="true"] .tamga-rail-section-caret {
|
|
421
|
+
opacity: 0.35;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.tamga-rail-group-body {
|
|
425
|
+
display: grid;
|
|
426
|
+
grid-template-rows: 1fr;
|
|
427
|
+
transition: grid-template-rows var(--duration-press) var(--ease-standard);
|
|
428
|
+
}
|
|
429
|
+
.tamga-rail-group-body > div {
|
|
430
|
+
display: flex;
|
|
431
|
+
flex-direction: column;
|
|
432
|
+
gap: 8px;
|
|
433
|
+
min-height: 0;
|
|
434
|
+
overflow: hidden;
|
|
435
|
+
}
|
|
436
|
+
.tamga-rail-group-body[data-open="false"] {
|
|
437
|
+
grid-template-rows: 0fr;
|
|
438
|
+
}
|
|
439
|
+
@media (prefers-reduced-motion: reduce) {
|
|
440
|
+
.tamga-rail-group-body,
|
|
441
|
+
.tamga-rail-section-caret {
|
|
442
|
+
transition: none;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
383
446
|
.tamga-rail-link {
|
|
384
447
|
position: relative;
|
|
385
448
|
display: flex;
|
|
@@ -79,11 +79,30 @@ export type AppShellProps = {
|
|
|
79
79
|
*/
|
|
80
80
|
mainClassName?: string;
|
|
81
81
|
linkComponent?: LinkComponent;
|
|
82
|
+
/**
|
|
83
|
+
* Section headers fold. On, each header in the wide rail is a button that hides its entries; the
|
|
84
|
+
* open page's section never folds, and a route landing inside a folded section finds it open.
|
|
85
|
+
* Off, headers are plain labels, the pre-0.6 rail. TR: Bölüm başlıkları katlanır. Açıkken geniş
|
|
86
|
+
* raydaki her başlık girişlerini gizleyen bir düğme; açık sayfanın bölümü hiç katlanmaz, kapalı
|
|
87
|
+
* bir bölüme düşen rota onu açık bulur. Kapalıyken başlıklar düz etiket, 0.6 öncesi ray.
|
|
88
|
+
*/
|
|
89
|
+
collapsibleSections?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The folded sections, by name. Given, the shell is controlled: a click only reports through
|
|
92
|
+
* `onSectionToggle`, and the product decides. Absent, the shell keeps the state itself and forgets
|
|
93
|
+
* it on reload · where a preference lives (browser, account, device) is the product's call.
|
|
94
|
+
* TR: Kapalı bölümler, adıyla. Verilirse kabuk kontrollü: tık yalnız `onSectionToggle` ile haber
|
|
95
|
+
* verir, kararı ürün verir. Verilmezse kabuk durumu kendi tutar ve yenilemede unutur · tercihin
|
|
96
|
+
* nerede yaşadığı (tarayıcı, hesap, cihaz) ürünün kararı.
|
|
97
|
+
*/
|
|
98
|
+
collapsedSections?: readonly string[];
|
|
99
|
+
/** Fires with the section name and whether it is now open. TR: Bölüm adı ve artık açık olup olmadığıyla ateşlenir. */
|
|
100
|
+
onSectionToggle?: (section: string, open: boolean) => void;
|
|
82
101
|
labels: {
|
|
83
102
|
home: string;
|
|
84
103
|
primaryNav: string;
|
|
85
104
|
};
|
|
86
105
|
children: ReactNode;
|
|
87
106
|
};
|
|
88
|
-
export declare function AppShell({ brand, nav, activePath, rail, topbar, railFooter, mainClassName, linkComponent: Link, labels, children, }: AppShellProps): import("react").JSX.Element;
|
|
107
|
+
export declare function AppShell({ brand, nav, activePath, rail, topbar, railFooter, mainClassName, linkComponent: Link, collapsibleSections, collapsedSections, onSectionToggle, labels, children, }: AppShellProps): import("react").JSX.Element;
|
|
89
108
|
//# sourceMappingURL=app-shell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../src/patterns/app-shell.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../src/patterns/app-shell.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAI7C,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,wKAAwK;IACxK,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1C;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,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;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,sHAAsH;IACtH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,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,EACb,aAAa,EAAE,IAAgB,EAC/B,mBAA0B,EAC1B,iBAAiB,EACjB,eAAe,EACf,MAAM,EACN,QAAQ,GACT,EAAE,aAAa,+BA+Kf"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Fragment } from "react";
|
|
3
|
+
import { Fragment, useId, useState } from "react";
|
|
4
4
|
import { Icon } from "../components/icon.js";
|
|
5
|
+
import { CaretDown } from "../components/icons.js";
|
|
5
6
|
import { RailLink } from "../components/rail-link.js";
|
|
6
7
|
import { cn } from "../lib/cn.js";
|
|
7
8
|
import { PlainLink } from "./shared.js";
|
|
8
|
-
export function AppShell({ brand, nav, activePath, rail = "narrow", topbar, railFooter, mainClassName, linkComponent: Link = PlainLink, labels, children, }) {
|
|
9
|
+
export function AppShell({ brand, nav, activePath, rail = "narrow", topbar, railFooter, mainClassName, linkComponent: Link = PlainLink, collapsibleSections = true, collapsedSections, onSectionToggle, labels, children, }) {
|
|
9
10
|
/* Bir giriş kendi alt ağacına sahip, ve EN UZUN EŞLEŞEN KAZANIR: kökün `/`
|
|
10
11
|
olmadığı bir panelde kök giriş altındaki her rotayı yutuyordu.
|
|
11
12
|
Gerekçe: docs/gerekce/08-blok-ve-sablon.md */
|
|
@@ -16,5 +17,38 @@ export function AppShell({ brand, nav, activePath, rail = "narrow", topbar, rail
|
|
|
16
17
|
/* ADRESİ OLMAYAN GİRİŞ ANAHTARIYLA EŞLEŞİYOR: masaüstü kabuğunda ekran bir durum, yol değil. */
|
|
17
18
|
const isCurrent = (entry) => entry.href ? entry.href === enOzel : entry.key === activePath;
|
|
18
19
|
const wide = rail === "wide";
|
|
19
|
-
|
|
20
|
+
/* KATLAMA. Kapalı liste ya üründen gelir (kontrollü) ya da burada tutulur.
|
|
21
|
+
AÇIK SAYFANIN BÖLÜMÜ HEP AÇIK: "neredeyim" raydan okunmalı · kapalı bir
|
|
22
|
+
bölüme yönlendirmeyle düşen kullanıcı da onu açık bulur, listeyi
|
|
23
|
+
değiştirmeden. Dar rayda başlık yok, dolayısıyla katlanacak şey de yok. */
|
|
24
|
+
const [icKapali, setIcKapali] = useState([]);
|
|
25
|
+
const kapali = collapsedSections ?? icKapali;
|
|
26
|
+
const aktifBolum = nav.find(isCurrent)?.section;
|
|
27
|
+
const acikMi = (bolum) => !wide || !collapsibleSections || bolum === aktifBolum || !kapali.includes(bolum);
|
|
28
|
+
const degistir = (bolum) => {
|
|
29
|
+
if (bolum === aktifBolum)
|
|
30
|
+
return;
|
|
31
|
+
const sonraki = !acikMi(bolum);
|
|
32
|
+
onSectionToggle?.(bolum, sonraki);
|
|
33
|
+
if (collapsedSections === undefined) {
|
|
34
|
+
setIcKapali((k) => (sonraki ? k.filter((x) => x !== bolum) : [...k, bolum]));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/* Ardışık girişler bölüm adına göre kümeleniyor; adı olmayanlar kendi
|
|
38
|
+
kümesinde, başlıksız. Veri düz kalıyor (bkz. `section`), küme çizim için. */
|
|
39
|
+
const gruplar = [];
|
|
40
|
+
for (const entry of nav) {
|
|
41
|
+
const son = gruplar[gruplar.length - 1];
|
|
42
|
+
if (son && son.bolum === entry.section)
|
|
43
|
+
son.girisler.push(entry);
|
|
44
|
+
else
|
|
45
|
+
gruplar.push({ bolum: entry.section, girisler: [entry] });
|
|
46
|
+
}
|
|
47
|
+
const kimlik = useId();
|
|
48
|
+
return (_jsxs("div", { className: "flex h-dvh w-full overflow-hidden bg-rail text-ink-soft", children: [_jsxs("aside", { className: `flex shrink-0 flex-col gap-2 bg-rail 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: gruplar.map((grup, gi) => {
|
|
49
|
+
const id = `${kimlik}-${gi}`;
|
|
50
|
+
const acik = grup.bolum ? acikMi(grup.bolum) : true;
|
|
51
|
+
const kilitli = grup.bolum !== undefined && grup.bolum === aktifBolum;
|
|
52
|
+
return (_jsxs(Fragment, { children: [grup.bolum ? (wide ? (collapsibleSections ? (_jsxs("button", { type: "button", className: cn("tamga-rail-section", gi > 0 && "mt-3"), "aria-expanded": acik, "aria-controls": id, "aria-disabled": kilitli || undefined, "data-locked": kilitli || undefined, onClick: () => degistir(grup.bolum), children: [grup.bolum, _jsx(Icon, { icon: CaretDown, size: "xs", weight: "bold", className: "tamga-rail-section-caret" })] })) : (_jsx("p", { className: `px-2.5 text-caption font-bold uppercase tracking-label text-ink-faint ${gi > 0 ? "mt-5 pb-1.5" : "pb-1.5"}`, children: grup.bolum }))) : gi > 0 ? (_jsx("span", { className: "my-1.5 h-0 w-6 shrink-0 border-t border-line", "aria-hidden": true })) : null) : null, _jsx("div", { id: id, className: "tamga-rail-group-body", "data-open": acik, inert: !acik, children: _jsx("div", { className: wide ? undefined : "items-center", children: grup.girisler.map((entry) => (_jsx(RailLink, { href: entry.href, onClick: entry.onSelect, label: entry.label, active: isCurrent(entry), showLabel: wide, linkComponent: Link, "data-nav": entry.key, children: _jsx(Icon, { icon: entry.icon, size: "md", weight: isCurrent(entry) ? "fill" : "regular" }) }, entry.key))) }) })] }, grup.bolum ?? `_${gi}`));
|
|
53
|
+
}) }), 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 bg-band py-4", children: topbar }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden pr-4 pb-4", children: _jsx("main", { className: cn("tamga-surface tamga-app-main relative h-full min-h-0 overflow-y-auto", mainClassName), children: children }) })] })] }));
|
|
20
54
|
}
|
package/dist/styles.css
CHANGED
package/dist/theme.css
CHANGED