@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 +15 -0
- package/dist/app/next.css +1 -0
- package/dist/app/routes/PageEditor.d.ts +10 -0
- package/dist/app/routes/PageEditor.js +20 -4
- package/package.json +1 -1
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.
|
|
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",
|