@cardinali-studio/adminkit 2.2.22 → 2.2.23

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/dist/app/app.css CHANGED
@@ -707,6 +707,14 @@ body { margin: 0; background: var(--ak-bg); color: var(--ak-ink); font-family: v
707
707
  overflow: hidden; padding-inline: 2px; color: #8a8a91; text-align: center;
708
708
  font: 400 11px/1.3 var(--ak-font); text-overflow: ellipsis; white-space: nowrap;
709
709
  }
710
+ .pe-photo-alt {
711
+ align-self: center; display: inline-flex; align-items: center; gap: 5px; min-height: 24px;
712
+ padding: 3px 8px; border: 0; border-radius: 999px; background: var(--ak-inset);
713
+ color: var(--ak-ink-quiet); cursor: pointer; font: 500 10px/1 var(--ak-font);
714
+ }
715
+ .pe-photo-alt strong { color: #a66a13; font: 700 9px/1 var(--ak-font-mono); letter-spacing: .04em; }
716
+ .pe-photo-alt.is-set strong { color: #28745a; }
717
+ .pe-photo-alt:hover, .pe-photo-alt:focus-visible { background: var(--ak-hover); color: var(--ak-ink); outline: 0; }
710
718
  .pe-photo-actions {
711
719
  position: absolute; z-index: 3; top: 0; left: 50%; transform: translate(-50%, -18%);
712
720
  display: flex; align-items: center; padding: 5px; border: 1px solid var(--ak-line);
@@ -744,6 +752,13 @@ body { margin: 0; background: var(--ak-bg); color: var(--ak-ink); font-family: v
744
752
  cursor: grabbing;
745
753
  }
746
754
  .pe-photo-overlay img { max-width: 100%; max-height: 100%; display: block; }
755
+ .pe-alt-modal { width: min(620px, 100%); }
756
+ .pe-alt-preview {
757
+ min-width: 0; display: grid; grid-template-columns: 92px minmax(0,1fr); gap: 14px; align-items: center;
758
+ padding: 10px; border-radius: 8px; background: var(--ak-inset);
759
+ }
760
+ .pe-alt-preview img { width: 92px; height: 72px; display: block; object-fit: contain; border-radius: 5px; background: var(--ak-bg); }
761
+ .pe-alt-preview span { overflow: hidden; color: var(--ak-ink-quiet); font: 500 12px/1.3 var(--ak-font); text-overflow: ellipsis; white-space: nowrap; }
747
762
 
748
763
  /* Il masonry verticale usa il flusso CSS a colonne: riempie una colonna dall'alto verso il
749
764
  basso prima di passare alla successiva. `.is-masonry` resta come alias visuale per markup
package/dist/app/next.css CHANGED
@@ -707,6 +707,7 @@
707
707
  border-radius: 10px;
708
708
  box-shadow: var(--ak-lift-3);
709
709
  }
710
+ :root[data-admin-interface="next"] .pe-photo-alt { border-radius: 999px; }
710
711
  /* Tutte le azioni primarie usano la coppia di colori del tema. Alcuni componenti storici
711
712
  * fissavano il testo a bianco: in dark l'accento diventa chiaro e richiede invece inchiostro
712
713
  * scuro, come il pulsante di pubblicazione. */
@@ -1,6 +1,9 @@
1
1
  import type { PickerOptions } from "../../form/MediaPicker.js";
2
+ import type { GalleryImage } from "../../index.js";
2
3
  import type { AssetFile } from "../../index.js";
3
4
  import type { PageModel } from "../lib/pages";
5
+ import { type GalleryLayout } from "../lib/gallery-layout";
6
+ type CuratedItem = GalleryImage;
4
7
  export declare function PageHeader({ page, pages, onChange, seoActive, onSeoToggle, languages, activeLanguage, onLanguageChange, }: {
5
8
  page: PageModel;
6
9
  pages: PageModel[];
@@ -15,6 +18,12 @@ export declare function PageHeader({ page, pages, onChange, seoActive, onSeoTogg
15
18
  activeLanguage?: string;
16
19
  onLanguageChange?: (language: string) => void;
17
20
  }): import("react").JSX.Element;
21
+ export declare function GalleryEditor({ value, onChange, pickMediaFiles, layout, }: {
22
+ value: CuratedItem[];
23
+ onChange: (items: CuratedItem[]) => void;
24
+ pickMediaFiles?: (o: PickerOptions) => Promise<AssetFile[] | null>;
25
+ layout?: GalleryLayout;
26
+ }): import("react").JSX.Element;
18
27
  export declare function PageEditor({ page, onChange, onRefresh, pristine, pickMedia, pickMediaFiles, activeLanguage, defaultLanguage, }: {
19
28
  page: PageModel;
20
29
  onChange: (next: PageModel) => void;
@@ -30,3 +39,4 @@ export declare function PageEditor({ page, onChange, onRefresh, pristine, pickMe
30
39
  activeLanguage?: string;
31
40
  defaultLanguage?: string;
32
41
  }): import("react").JSX.Element;
42
+ export {};
@@ -101,7 +101,7 @@ function imageName(src) {
101
101
  return raw;
102
102
  }
103
103
  }
104
- function GalleryCard({ item, verticalMasonry = false, sortable = true, activeId, overId, activeIndex, overIndex, onReplace, onToggle, onRemove, }) {
104
+ function GalleryCard({ item, verticalMasonry = false, sortable = true, activeId, overId, activeIndex, overIndex, onReplace, onEditAlt, onToggle, onRemove, }) {
105
105
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
106
106
  id: item.src,
107
107
  disabled: !sortable,
@@ -119,9 +119,9 @@ function GalleryCard({ item, verticalMasonry = false, sortable = true, activeId,
119
119
  isDragging ? "is-dragging" : "",
120
120
  insertBefore ? "is-insert-before" : "",
121
121
  insertAfter ? "is-insert-after" : "",
122
- ].filter(Boolean).join(" "), style: sortable ? { transform: CSS.Transform.toString(transform), transition } : undefined, children: [_jsxs("div", { className: "pe-photo-frame", style: frameStyle, ...(sortable ? attributes : {}), ...(sortable ? listeners : {}), children: [_jsx("img", { src: item.src, alt: imageName(item.src), draggable: false }), item.hidden && _jsx("span", { className: "pe-hidden-label", children: "Nascosta" })] }), _jsxs("div", { className: "pe-photo-actions", children: [_jsx("button", { type: "button", onClick: onReplace, title: "Cambia immagine", "aria-label": "Cambia immagine", children: _jsx(Icon.replace, {}) }), _jsx("button", { type: "button", onClick: onToggle, title: item.hidden ? "Rimetti online" : "Nascondi dalla gallery", "aria-label": item.hidden ? "Rimetti online" : "Nascondi dalla gallery", children: item.hidden ? _jsx(Icon.eye, {}) : _jsx(Icon.eyeOff, {}) }), _jsx("button", { type: "button", className: "is-danger", onClick: onRemove, title: "Rimuovi dalla gallery", "aria-label": "Rimuovi dalla gallery", children: _jsx(Icon.trash, {}) })] }), _jsx("figcaption", { title: imageName(item.src), children: imageName(item.src) })] }));
122
+ ].filter(Boolean).join(" "), style: sortable ? { transform: CSS.Transform.toString(transform), transition } : undefined, children: [_jsxs("div", { className: "pe-photo-frame", style: frameStyle, ...(sortable ? attributes : {}), ...(sortable ? listeners : {}), children: [_jsx("img", { src: item.src, alt: imageName(item.src), draggable: false }), item.hidden && _jsx("span", { className: "pe-hidden-label", children: "Nascosta" })] }), _jsxs("div", { className: "pe-photo-actions", children: [_jsx("button", { type: "button", onClick: onReplace, title: "Cambia immagine", "aria-label": "Cambia immagine", children: _jsx(Icon.replace, {}) }), _jsx("button", { type: "button", onClick: onToggle, title: item.hidden ? "Rimetti online" : "Nascondi dalla gallery", "aria-label": item.hidden ? "Rimetti online" : "Nascondi dalla gallery", children: item.hidden ? _jsx(Icon.eye, {}) : _jsx(Icon.eyeOff, {}) }), _jsx("button", { type: "button", className: "is-danger", onClick: onRemove, title: "Rimuovi dalla gallery", "aria-label": "Rimuovi dalla gallery", children: _jsx(Icon.trash, {}) })] }), _jsx("figcaption", { title: imageName(item.src), children: imageName(item.src) }), _jsxs("button", { type: "button", className: `pe-photo-alt${item.alt?.trim() ? " is-set" : ""}`, onClick: onEditAlt, "aria-label": `Modifica testo alternativo di ${imageName(item.src)}`, children: [_jsx("strong", { children: "ALT" }), _jsx("span", { children: item.alt?.trim() ? "Modifica" : "Vuoto" })] })] }));
123
123
  }
124
- function GalleryEditor({ value, onChange, pickMediaFiles, layout = "grid", }) {
124
+ export function GalleryEditor({ value, onChange, pickMediaFiles, layout = "grid", }) {
125
125
  const list = Array.isArray(value) ? value : [];
126
126
  const masonry = layout !== "grid";
127
127
  const verticalMasonry = layout === "masonry-vertical";
@@ -129,6 +129,7 @@ function GalleryEditor({ value, onChange, pickMediaFiles, layout = "grid", }) {
129
129
  const hiddenItems = masonry ? list.filter((item) => item.hidden) : [];
130
130
  const [activeId, setActiveId] = useState(null);
131
131
  const [overId, setOverId] = useState(null);
132
+ const [editingAlt, setEditingAlt] = useState(null);
132
133
  const [addPositionOpen, setAddPositionOpen] = useState(false);
133
134
  const addPositionRef = useRef(null);
134
135
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
@@ -215,7 +216,22 @@ function GalleryEditor({ value, onChange, pickMediaFiles, layout = "grid", }) {
215
216
  const remove = (src) => onChange(list.filter((item) => item.src !== src));
216
217
  return (_jsxs("section", { className: `pe-gallery${masonry ? ` is-${layout}` : ""}`, "aria-label": "Foto della gallery", children: [_jsxs("div", { className: "pe-gallery-bar", children: [_jsxs("div", { className: "pe-gallery-summary", children: [_jsxs("strong", { children: [list.length, " ", list.length === 1 ? "foto" : "foto"] }), _jsx("span", { children: list.length === 0
217
218
  ? "Scegli le immagini da mostrare nella pagina."
218
- : `${visible} online${visible < list.length ? ` · ${list.length - visible} nascoste` : ""}` }), layout === "masonry-vertical" && list.length > 0 && (_jsx("span", { className: "pe-gallery-layout-note", children: "Masonry verticale \u00B7 3 colonne \u00B7 ordine dall\u2019alto verso il basso" })), layout === "masonry-horizontal" && list.length > 0 && (_jsx("span", { className: "pe-gallery-layout-note", children: "Masonry orizzontale \u00B7 6 colonne \u00B7 ordine da sinistra a destra" }))] }), list.length === 0 ? (_jsxs("button", { type: "button", className: "pe-add", onClick: () => void add("start"), children: ["Aggiungi foto ", _jsx(Icon.plus, {})] })) : (_jsxs("div", { className: "pe-add-group", ref: addPositionRef, children: [_jsxs("button", { type: "button", className: "pe-add", onClick: () => void add("start"), children: ["Aggiungi foto ", _jsx(Icon.plus, {})] }), _jsx("button", { type: "button", className: "pe-add-position", "aria-label": "Scegli dove aggiungere le foto", "aria-expanded": addPositionOpen, "aria-controls": "gallery-add-position", onClick: () => setAddPositionOpen((open) => !open), children: "\u2304" }), addPositionOpen && (_jsx("div", { className: "pe-add-menu", id: "gallery-add-position", children: _jsx("button", { type: "button", onClick: () => void add("end"), children: "Aggiungi in fondo" }) }))] }))] }), list.length === 0 ? (_jsxs("button", { type: "button", className: "pe-gallery-empty", onClick: () => void add("start"), children: [_jsx(Icon.gallery, {}), _jsx("strong", { children: "La gallery \u00E8 vuota" }), _jsx("span", { children: "Aggiungi le prime fotografie" })] })) : (_jsxs(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragStart: ({ active }) => setActiveId(String(active.id)), onDragOver: ({ over }) => setOverId(over ? String(over.id) : null), onDragCancel: () => { setActiveId(null); setOverId(null); }, onDragEnd: finishDrag, children: [_jsx(SortableContext, { items: shownItems.map((item) => item.src), strategy: rectSortingStrategy, children: _jsx("div", { className: "pe-photo-grid", children: shownItems.map((item) => (_jsx(GalleryCard, { item: item, verticalMasonry: verticalMasonry, activeId: activeId, overId: overId, activeIndex: activeIndex, overIndex: overIndex, onReplace: () => void replace(itemIndex(item.src)), onToggle: () => toggle(item.src), onRemove: () => remove(item.src) }, item.src))) }) }), _jsx(DragOverlay, { children: activeItem && (_jsx("div", { className: "pe-photo-overlay", children: _jsx("img", { src: activeItem.src, alt: "" }) })) }), masonry && hiddenItems.length > 0 && (_jsxs("section", { className: "pe-hidden-gallery", "aria-label": "Foto nascoste", children: [_jsxs("div", { className: "pe-hidden-gallery-head", children: [_jsx("strong", { children: "Nascoste" }), _jsx("span", { children: "Non occupano spazio nella gallery pubblica." })] }), _jsx("div", { className: "pe-hidden-grid", children: hiddenItems.map((item) => (_jsx(GalleryCard, { item: item, sortable: false, activeId: activeId, overId: overId, activeIndex: activeIndex, overIndex: overIndex, onReplace: () => void replace(itemIndex(item.src)), onToggle: () => toggle(item.src), onRemove: () => remove(item.src) }, item.src))) })] }))] }))] }));
219
+ : `${visible} online${visible < list.length ? ` · ${list.length - visible} nascoste` : ""}` }), layout === "masonry-vertical" && list.length > 0 && (_jsx("span", { className: "pe-gallery-layout-note", children: "Masonry verticale \u00B7 3 colonne \u00B7 ordine dall\u2019alto verso il basso" })), layout === "masonry-horizontal" && list.length > 0 && (_jsx("span", { className: "pe-gallery-layout-note", children: "Masonry orizzontale \u00B7 6 colonne \u00B7 ordine da sinistra a destra" }))] }), list.length === 0 ? (_jsxs("button", { type: "button", className: "pe-add", onClick: () => void add("start"), children: ["Aggiungi foto ", _jsx(Icon.plus, {})] })) : (_jsxs("div", { className: "pe-add-group", ref: addPositionRef, children: [_jsxs("button", { type: "button", className: "pe-add", onClick: () => void add("start"), children: ["Aggiungi foto ", _jsx(Icon.plus, {})] }), _jsx("button", { type: "button", className: "pe-add-position", "aria-label": "Scegli dove aggiungere le foto", "aria-expanded": addPositionOpen, "aria-controls": "gallery-add-position", onClick: () => setAddPositionOpen((open) => !open), children: "\u2304" }), addPositionOpen && (_jsx("div", { className: "pe-add-menu", id: "gallery-add-position", children: _jsx("button", { type: "button", onClick: () => void add("end"), children: "Aggiungi in fondo" }) }))] }))] }), list.length === 0 ? (_jsxs("button", { type: "button", className: "pe-gallery-empty", onClick: () => void add("start"), children: [_jsx(Icon.gallery, {}), _jsx("strong", { children: "La gallery \u00E8 vuota" }), _jsx("span", { children: "Aggiungi le prime fotografie" })] })) : (_jsxs(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragStart: ({ active }) => setActiveId(String(active.id)), onDragOver: ({ over }) => setOverId(over ? String(over.id) : null), onDragCancel: () => { setActiveId(null); setOverId(null); }, onDragEnd: finishDrag, children: [_jsx(SortableContext, { items: shownItems.map((item) => item.src), strategy: rectSortingStrategy, children: _jsx("div", { className: "pe-photo-grid", children: shownItems.map((item) => (_jsx(GalleryCard, { item: item, verticalMasonry: verticalMasonry, activeId: activeId, overId: overId, activeIndex: activeIndex, overIndex: overIndex, onReplace: () => void replace(itemIndex(item.src)), onEditAlt: () => setEditingAlt({ src: item.src, value: item.alt ?? "" }), onToggle: () => toggle(item.src), onRemove: () => remove(item.src) }, item.src))) }) }), _jsx(DragOverlay, { children: activeItem && (_jsx("div", { className: "pe-photo-overlay", children: _jsx("img", { src: activeItem.src, alt: "" }) })) }), masonry && hiddenItems.length > 0 && (_jsxs("section", { className: "pe-hidden-gallery", "aria-label": "Foto nascoste", children: [_jsxs("div", { className: "pe-hidden-gallery-head", children: [_jsx("strong", { children: "Nascoste" }), _jsx("span", { children: "Non occupano spazio nella gallery pubblica." })] }), _jsx("div", { className: "pe-hidden-grid", children: hiddenItems.map((item) => (_jsx(GalleryCard, { item: item, sortable: false, activeId: activeId, overId: overId, activeIndex: activeIndex, overIndex: overIndex, onReplace: () => void replace(itemIndex(item.src)), onEditAlt: () => setEditingAlt({ src: item.src, value: item.alt ?? "" }), onToggle: () => toggle(item.src), onRemove: () => remove(item.src) }, item.src))) })] }))] })), editingAlt && (_jsx("div", { className: "pe-modal", role: "presentation", onMouseDown: (event) => {
220
+ if (event.target === event.currentTarget)
221
+ setEditingAlt(null);
222
+ }, children: _jsxs("div", { className: "pe-modal-card pe-alt-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "pe-alt-dialog", children: [_jsxs("header", { children: [_jsx("h2", { id: "pe-alt-dialog", children: "Testo alternativo" }), _jsx("button", { type: "button", onClick: () => setEditingAlt(null), "aria-label": "Chiudi", children: "\u00D7" })] }), _jsxs("div", { className: "pe-modal-fields", children: [_jsxs("div", { className: "pe-alt-preview", children: [_jsx("img", { src: editingAlt.src, alt: "" }), _jsx("span", { children: imageName(editingAlt.src) })] }), _jsxs("label", { children: [_jsx("span", { children: "Descrizione dell\u2019immagine" }), _jsx("input", { autoFocus: true, value: editingAlt.value, maxLength: 300, placeholder: "Es. Chef che impiatta un dessert al cioccolato", onChange: (event) => setEditingAlt({ ...editingAlt, value: event.target.value }), onKeyDown: (event) => {
223
+ if (event.key !== "Enter")
224
+ return;
225
+ onChange(list.map((item) => item.src === editingAlt.src
226
+ ? { ...item, alt: editingAlt.value.trim() }
227
+ : item));
228
+ setEditingAlt(null);
229
+ } }), _jsx("small", { children: "Descrivi ci\u00F2 che \u00E8 utile nel contesto della pagina. Lascialo vuoto soltanto se la foto \u00E8 decorativa." })] })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: "ak-btn", onClick: () => setEditingAlt(null), children: "Annulla" }), _jsx("button", { type: "button", className: "ak-btn ak-btn-primary", onClick: () => {
230
+ onChange(list.map((item) => item.src === editingAlt.src
231
+ ? { ...item, alt: editingAlt.value.trim() }
232
+ : item));
233
+ setEditingAlt(null);
234
+ }, children: "Applica" })] })] }) }))] }));
219
235
  }
220
236
  export function PageEditor({ page, onChange, onRefresh, pristine, pickMedia, pickMediaFiles, activeLanguage, defaultLanguage, }) {
221
237
  const [bodyState, setBodyState] = useState("idle");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardinali-studio/adminkit",
3
- "version": "2.2.22",
3
+ "version": "2.2.23",
4
4
  "type": "module",
5
5
  "description": "Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.",
6
6
  "license": "UNLICENSED",