@jhits/botanics_and_you 0.0.1 → 0.0.3

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.
Files changed (41) hide show
  1. package/dist/index.d.ts +10 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +9 -0
  4. package/dist/plugin-blog/api-config.d.ts +4 -0
  5. package/dist/plugin-blog/api-config.d.ts.map +1 -0
  6. package/dist/plugin-blog/api-config.js +7 -0
  7. package/dist/plugin-blog/blocks/CategoryDropdown.d.ts +10 -0
  8. package/dist/plugin-blog/blocks/CategoryDropdown.d.ts.map +1 -0
  9. package/dist/plugin-blog/blocks/CategoryDropdown.js +67 -0
  10. package/dist/plugin-blog/blocks/Heading.d.ts +3 -0
  11. package/dist/plugin-blog/blocks/Heading.d.ts.map +1 -0
  12. package/dist/plugin-blog/blocks/Heading.js +83 -0
  13. package/dist/plugin-blog/blocks/Hero.d.ts +17 -0
  14. package/dist/plugin-blog/blocks/Hero.d.ts.map +1 -0
  15. package/dist/plugin-blog/blocks/Hero.js +84 -0
  16. package/dist/plugin-blog/blocks/Image.d.ts +6 -0
  17. package/dist/plugin-blog/blocks/Image.d.ts.map +1 -0
  18. package/dist/plugin-blog/blocks/Image.js +285 -0
  19. package/dist/plugin-blog/blocks/List.d.ts +3 -0
  20. package/dist/plugin-blog/blocks/List.d.ts.map +1 -0
  21. package/dist/plugin-blog/blocks/List.js +215 -0
  22. package/dist/plugin-blog/blocks/Paragraph.d.ts +3 -0
  23. package/dist/plugin-blog/blocks/Paragraph.d.ts.map +1 -0
  24. package/dist/plugin-blog/blocks/Paragraph.js +64 -0
  25. package/dist/plugin-blog/blocks/Recipe.d.ts +13 -0
  26. package/dist/plugin-blog/blocks/Recipe.d.ts.map +1 -0
  27. package/dist/plugin-blog/blocks/Recipe.js +136 -0
  28. package/dist/plugin-blog/blocks/Table.d.ts +6 -0
  29. package/dist/plugin-blog/blocks/Table.d.ts.map +1 -0
  30. package/dist/plugin-blog/blocks/Table.js +115 -0
  31. package/dist/plugin-blog/blocks.d.ts +14 -0
  32. package/dist/plugin-blog/blocks.d.ts.map +1 -0
  33. package/dist/plugin-blog/blocks.js +27 -0
  34. package/dist/plugin-blog/index.d.ts +17 -0
  35. package/dist/plugin-blog/index.d.ts.map +1 -0
  36. package/dist/plugin-blog/index.js +18 -0
  37. package/dist/plugin-blog/theme.d.ts +19 -0
  38. package/dist/plugin-blog/theme.d.ts.map +1 -0
  39. package/dist/plugin-blog/theme.js +28 -0
  40. package/package.json +27 -17
  41. package/tsconfig.json +0 -23
@@ -0,0 +1,10 @@
1
+ /**
2
+ * BotanicsAndYou Client Configuration for JHITS
3
+ *
4
+ * This package contains all client-specific plugin configurations.
5
+ *
6
+ * Usage:
7
+ * import { blogPluginConfig, botanyBlocks } from '@jhits/BotanicsAndYou/plugin-blog';
8
+ */
9
+ export * from './plugin-blog';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * BotanicsAndYou Client Configuration for JHITS
3
+ *
4
+ * This package contains all client-specific plugin configurations.
5
+ *
6
+ * Usage:
7
+ * import { blogPluginConfig, botanyBlocks } from '@jhits/BotanicsAndYou/plugin-blog';
8
+ */
9
+ export * from './plugin-blog';
@@ -0,0 +1,4 @@
1
+ declare const API_BASE_URL: string;
2
+ export declare function createApiUrl(path: string): string;
3
+ export { API_BASE_URL };
4
+ //# sourceMappingURL=api-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-config.d.ts","sourceRoot":"","sources":["../../src/plugin-blog/api-config.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,YAAY,QACgF,CAAC;AAEnG,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ // API Configuration for BotanicsAndYou package
2
+ const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL
3
+ || (process.env.NODE_ENV === 'production' ? 'https://your-domain.com' : 'http://localhost:3001');
4
+ export function createApiUrl(path) {
5
+ return `${API_BASE_URL}/api${path}`;
6
+ }
7
+ export { API_BASE_URL };
@@ -0,0 +1,10 @@
1
+ export interface CategoryDropdownProps {
2
+ value: string;
3
+ onChange: (category: string) => void;
4
+ existingCategories: string[];
5
+ className?: string;
6
+ placeholder?: string;
7
+ showLabel?: boolean;
8
+ }
9
+ export declare function CategoryDropdown({ value, onChange, existingCategories, className, placeholder, showLabel, }: CategoryDropdownProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=CategoryDropdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryDropdown.d.ts","sourceRoot":"","sources":["../../../src/plugin-blog/blocks/CategoryDropdown.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,kBAAuB,EACvB,SAAc,EACd,WAAyC,EACzC,SAAiB,GACpB,EAAE,qBAAqB,2CAiJvB"}
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useRef } from 'react';
4
+ import { ChevronDown, Plus } from 'lucide-react';
5
+ export function CategoryDropdown({ value, onChange, existingCategories = [], className = '', placeholder = 'Select or add category...', showLabel = false, }) {
6
+ const [isOpen, setIsOpen] = useState(false);
7
+ const [inputValue, setInputValue] = useState('');
8
+ const [isAddingNew, setIsAddingNew] = useState(false);
9
+ const dropdownRef = useRef(null);
10
+ const inputRef = useRef(null);
11
+ // This ref tracks if we have successfully set an initial value
12
+ // to avoid snapping back to the first item if the user manually clears the field
13
+ const hasAutoSelected = useRef(false);
14
+ /**
15
+ * AUTO-SELECT LOGIC
16
+ * We watch 'existingCategories'. As soon as it populates and we have no value, we pick the first.
17
+ */
18
+ useEffect(() => {
19
+ const isValueEmpty = !value || value.trim() === '';
20
+ if (isValueEmpty && existingCategories.length > 0 && !hasAutoSelected.current) {
21
+ const firstCategory = existingCategories.find(cat => cat && cat.trim() !== '');
22
+ if (firstCategory) {
23
+ // Set the flag FIRST to prevent race conditions
24
+ hasAutoSelected.current = true;
25
+ // Use requestAnimationFrame to ensure we are outside the
26
+ // render phase of the category fetch completion
27
+ requestAnimationFrame(() => {
28
+ onChange(firstCategory);
29
+ });
30
+ }
31
+ }
32
+ // If a value is manually set (by user or saved data),
33
+ // we mark auto-select as "done" so it doesn't fight the user
34
+ if (!isValueEmpty) {
35
+ hasAutoSelected.current = true;
36
+ }
37
+ }, [existingCategories, value, onChange]);
38
+ // UI filtering
39
+ const otherCategories = existingCategories.filter(cat => cat && cat.trim() !== '' && cat !== value);
40
+ // Outside Click Handler
41
+ useEffect(() => {
42
+ const handleClickOutside = (event) => {
43
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
44
+ setIsOpen(false);
45
+ setIsAddingNew(false);
46
+ setInputValue('');
47
+ }
48
+ };
49
+ if (isOpen) {
50
+ document.addEventListener('mousedown', handleClickOutside);
51
+ return () => document.removeEventListener('mousedown', handleClickOutside);
52
+ }
53
+ }, [isOpen]);
54
+ // Focus on new input
55
+ useEffect(() => {
56
+ if (isAddingNew && inputRef.current) {
57
+ inputRef.current.focus();
58
+ }
59
+ }, [isAddingNew]);
60
+ const handleSelect = (category) => {
61
+ onChange(category);
62
+ setIsOpen(false);
63
+ setIsAddingNew(false);
64
+ setInputValue('');
65
+ };
66
+ return (_jsxs("div", { className: `relative ${className}`, ref: dropdownRef, children: [showLabel && (_jsx("label", { className: "text-[10px] text-neutral-500 uppercase font-bold block mb-2 tracking-widest", children: "Category" })), _jsxs("div", { className: "relative", children: [_jsxs("div", { onClick: () => setIsOpen(!isOpen), className: "w-full bg-transparent border border-sage/20 rounded-full px-4 py-1.5 text-xs font-bold uppercase tracking-[0.2em] text-sage flex items-center justify-between cursor-pointer hover:bg-sage/5 transition-all", children: [_jsx("span", { className: "truncate", children: value || placeholder }), _jsx(ChevronDown, { size: 14, className: `transition-transform text-sage/50 ${isOpen ? 'rotate-180' : ''}` })] }), isOpen && (_jsxs("div", { className: "absolute z-50 w-full mt-2 bg-white dark:bg-neutral-900 rounded-xl shadow-xl border border-neutral-100 dark:border-neutral-800 overflow-hidden animate-in fade-in zoom-in-95 duration-150", children: [otherCategories.length > 0 && (_jsx("div", { className: "py-1 max-h-48 overflow-y-auto", children: otherCategories.map((category) => (_jsx("button", { type: "button", onClick: () => handleSelect(category), className: "w-full text-left px-4 py-2.5 text-xs font-bold text-neutral-700 dark:text-neutral-300 hover:bg-sage/5 hover:text-sage transition-colors", children: category }, category))) })), !isAddingNew ? (_jsxs("button", { type: "button", onClick: () => setIsAddingNew(true), className: "w-full text-left px-4 py-3 text-xs font-bold text-sage hover:bg-primary/5 transition-colors border-t border-neutral-50 dark:border-neutral-800 flex items-center gap-2", children: [_jsx(Plus, { size: 14 }), " Add new category"] })) : (_jsxs("div", { className: "p-3 border-t border-neutral-50 dark:border-neutral-800 bg-neutral-50/50 dark:bg-neutral-800/50", children: [_jsx("input", { ref: inputRef, value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyDown: (e) => e.key === 'Enter' && inputValue.trim() && handleSelect(inputValue.trim()), placeholder: "Category name...", className: "w-full border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-900 p-2 rounded-lg text-xs font-bold outline-none focus:border-sage mb-2" }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { onClick: () => inputValue.trim() && handleSelect(inputValue.trim()), className: "flex-1 bg-sage text-white px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider", children: "Add" }), _jsx("button", { onClick: () => { setIsAddingNew(false); setInputValue(''); }, className: "px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase border border-neutral-200 dark:border-neutral-700 text-neutral-500", children: "Cancel" })] })] }))] }))] })] }));
67
+ }
@@ -0,0 +1,3 @@
1
+ import { ClientBlockDefinition } from "@jhits/plugin-blog";
2
+ export declare const HeadingBlock: ClientBlockDefinition;
3
+ //# sourceMappingURL=Heading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Heading.d.ts","sourceRoot":"","sources":["../../../src/plugin-blog/blocks/Heading.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAqC,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAmH9F,eAAO,MAAM,YAAY,EAAE,qBAoB1B,CAAC"}
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ import { Heading, ChevronDown } from "lucide-react";
4
+ import { useState, useRef, useEffect } from "react";
5
+ const getClassName = (level) => `
6
+ font-serif text-forest
7
+ ${level === 1 ? "text-5xl lg:text-6xl mt-12 mb-8 font-bold" : ""}
8
+ ${level === 2 ? "text-4xl lg:text-5xl mt-10 mb-2" : ""}
9
+ ${level === 3 ? "text-3xl mt-8 mb-4 text-forest/90" : ""}
10
+ ${level === 4 ? "text-xl mt-6 mb-3 font-medium text-sage italic" : ""}
11
+ ${level === 5 ? "text-lg mt-4 mb-2 font-medium text-sage" : ""}
12
+ ${level === 6 ? "text-base mt-3 mb-2 font-medium text-sage/80" : ""}
13
+ `;
14
+ /**
15
+ * Heading Block
16
+ * Simple heading block for botanical articles with SEO-optimized heading levels
17
+ */
18
+ const HeadingEdit = ({ block, onUpdate, isSelected }) => {
19
+ const level = block.data.level || 2;
20
+ const text = block.data.text || '';
21
+ const [showLevelDropdown, setShowLevelDropdown] = useState(false);
22
+ const dropdownRef = useRef(null);
23
+ const defaultClassName = getClassName(level);
24
+ // Close dropdown when clicking outside
25
+ useEffect(() => {
26
+ const handleClickOutside = (event) => {
27
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
28
+ setShowLevelDropdown(false);
29
+ }
30
+ };
31
+ if (showLevelDropdown) {
32
+ document.addEventListener('mousedown', handleClickOutside);
33
+ return () => document.removeEventListener('mousedown', handleClickOutside);
34
+ }
35
+ }, [showLevelDropdown]);
36
+ const handleLevelChange = (newLevel) => {
37
+ onUpdate({ level: newLevel });
38
+ setShowLevelDropdown(false);
39
+ };
40
+ return (_jsx("div", { className: "relative group", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "relative", ref: dropdownRef, children: [_jsxs("button", { type: "button", onClick: () => setShowLevelDropdown(!showLevelDropdown), className: "flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gray-200 bg-white hover:bg-gray-50 transition-colors text-xs font-bold text-gray-600", title: "Select heading level for SEO", children: [_jsxs("span", { children: ["H", level] }), _jsx(ChevronDown, { size: 12, className: `transition-transform ${showLevelDropdown ? 'rotate-180' : ''}` })] }), showLevelDropdown && (_jsx("div", { className: "absolute z-50 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden min-w-[120px]", children: [1, 2, 3, 4, 5, 6].map((lvl) => (_jsxs("button", { type: "button", onClick: () => handleLevelChange(lvl), className: `w-full text-left px-3 py-2 text-xs font-bold transition-colors ${level === lvl
41
+ ? 'bg-primary/10 text-primary'
42
+ : 'text-gray-700 hover:bg-gray-50'}`, children: ["H", lvl, " ", lvl === 1 ? '(Page Title)' : lvl === 2 ? '(Section)' : lvl === 3 ? '(Subsection)' : ''] }, lvl))) }))] }), _jsx("input", { type: "text", value: text, onChange: (e) => onUpdate({ text: e.target.value }), placeholder: "Enter heading text...", className: `flex-1 ${defaultClassName}` })] }) }));
43
+ };
44
+ const HeadingPreview = ({ block }) => {
45
+ const level = Math.min(Math.max(block.data.level || 2, 1), 6);
46
+ const text = block.data.text || '';
47
+ const defaultClassName = getClassName(level);
48
+ switch (level) {
49
+ case 1:
50
+ return _jsx("h1", { className: defaultClassName, children: text });
51
+ case 3:
52
+ return _jsx("h3", { className: defaultClassName, children: text });
53
+ case 4:
54
+ return _jsx("h4", { className: defaultClassName, children: text });
55
+ case 5:
56
+ return _jsx("h5", { className: defaultClassName, children: text });
57
+ case 6:
58
+ return _jsx("h6", { className: defaultClassName, children: text });
59
+ default:
60
+ return _jsx("h2", { className: defaultClassName, children: text });
61
+ }
62
+ };
63
+ export const HeadingBlock = {
64
+ type: 'heading',
65
+ name: 'Heading',
66
+ description: 'Add a heading (H1-H6)',
67
+ icon: Heading,
68
+ defaultData: {
69
+ text: '',
70
+ level: 2,
71
+ },
72
+ category: 'text',
73
+ validate: (data) => {
74
+ return typeof data.text === 'string' &&
75
+ typeof data.level === 'number' &&
76
+ data.level >= 1 && data.level <= 6;
77
+ },
78
+ components: {
79
+ Edit: HeadingEdit,
80
+ Preview: HeadingPreview,
81
+ Icon: Heading,
82
+ },
83
+ };
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { BlockEditProps, BlockPreviewProps, ClientBlockDefinition } from '@jhits/plugin-blog';
3
+ /**
4
+ * 2. Hero Edit Component
5
+ */
6
+ export declare const HeroEdit: React.FC<BlockEditProps>;
7
+ /**
8
+ * 3. Hero Preview Component
9
+ */
10
+ export declare const HeroPreview: React.FC<BlockPreviewProps & {
11
+ fallbackImage?: {
12
+ id?: string;
13
+ src?: string;
14
+ };
15
+ }>;
16
+ export declare const heroBlock: ClientBlockDefinition;
17
+ //# sourceMappingURL=Hero.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Hero.d.ts","sourceRoot":"","sources":["../../../src/plugin-blog/blocks/Hero.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAe9F;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAsI7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,GAAG;IAAE,aAAa,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CA6CvG,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,qBAkBvB,CAAC"}
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Image as ImageIcon } from 'lucide-react';
6
+ import { useCategories } from '@jhits/plugin-blog';
7
+ import { ImagePicker, Image as PluginImage } from '@jhits/plugin-images';
8
+ import { CategoryDropdown } from './CategoryDropdown';
9
+ import { createApiUrl } from '../api-config';
10
+ /**
11
+ * 2. Hero Edit Component
12
+ */
13
+ export const HeroEdit = ({ block, onUpdate }) => {
14
+ const [showImagePicker, setShowImagePicker] = useState(false);
15
+ const [currentBrightness, setCurrentBrightness] = useState(100);
16
+ const [currentBlur, setCurrentBlur] = useState(0);
17
+ const [mounted, setMounted] = useState(false);
18
+ const { categories } = useCategories();
19
+ // Handle SSR - ensure we only render portal on client
20
+ useEffect(() => {
21
+ setMounted(true);
22
+ }, []);
23
+ const data = (block.data || {});
24
+ const title = data.title || '';
25
+ const category = data.category || '';
26
+ const summary = data.summary || '';
27
+ const imageId = data.imageId;
28
+ // Sync effects
29
+ useEffect(() => {
30
+ if (imageId) {
31
+ fetch(createApiUrl(`/plugin-images/resolve?id=${encodeURIComponent(imageId)}`))
32
+ .then(res => res.ok ? res.json() : null)
33
+ .then(resolved => {
34
+ var _a, _b, _c, _d;
35
+ if (resolved) {
36
+ setCurrentBrightness((_b = (_a = data.brightness) !== null && _a !== void 0 ? _a : resolved.brightness) !== null && _b !== void 0 ? _b : 100);
37
+ setCurrentBlur((_d = (_c = data.blur) !== null && _c !== void 0 ? _c : resolved.blur) !== null && _d !== void 0 ? _d : 0);
38
+ }
39
+ })
40
+ .catch(() => {
41
+ setCurrentBrightness(100);
42
+ setCurrentBlur(0);
43
+ });
44
+ }
45
+ }, [imageId, data.brightness, data.blur]);
46
+ const updateField = (updates) => {
47
+ onUpdate(Object.assign(Object.assign({}, data), updates));
48
+ };
49
+ return (_jsxs("section", { className: "relative pt-32 pb-20 overflow-hidden group/hero", children: [_jsxs("div", { className: "max-w-7xl mx-auto px-6 lg:px-12 grid lg:grid-cols-2 gap-16 items-center", children: [_jsxs("div", { className: "order-2 lg:order-1 text-left flex flex-col items-start", children: [_jsx(CategoryDropdown, { value: category, onChange: (newCategory) => updateField({ category: newCategory }), existingCategories: categories, placeholder: "CATEGORIE" }), _jsx("textarea", { value: title, onChange: (e) => updateField({ title: e.target.value }), placeholder: "Artikel Titel...", rows: 2, className: "w-full text-5xl lg:text-7xl font-serif text-forest mb-8 leading-[1.1] tracking-tight bg-transparent border-none outline-none focus:ring-0 p-0 resize-none placeholder:text-gray-200 overflow-hidden" }), _jsx("textarea", { value: summary, onChange: (e) => updateField({ summary: e.target.value }), placeholder: "Klik om een samenvatting te typen...", rows: 3, className: "w-full text-lg lg:text-xl text-gray-600 leading-relaxed font-light bg-transparent border-none outline-none focus:ring-0 p-0 resize-none placeholder:text-gray-300" })] }), _jsx("div", { className: "order-1 lg:order-2 w-full", children: _jsxs("div", { className: "relative rounded-3xl overflow-hidden shadow-2xl aspect-[4/5] lg:aspect-[3/4] bg-gray-50 border border-gray-100 cursor-pointer group/imgcontainer", onClick: () => setShowImagePicker(true), children: [imageId ? (_jsx(PluginImage, { id: imageId, alt: title || 'Hero', fill: true, sizes: "(max-width: 1024px) 100vw, 50vw", className: "object-cover w-full h-full transition-transform duration-700 group-hover/imgcontainer:scale-105", editable: false })) : (_jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center text-gray-400", children: [_jsx(ImageIcon, { size: 48, className: "mb-2" }), _jsx("p", { className: "text-sm font-bold", children: "Add Image" })] })), _jsx("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover/imgcontainer:opacity-100 transition-opacity flex items-center justify-center z-10", children: _jsxs("div", { className: "bg-white px-4 py-2 rounded-full flex items-center gap-2", children: [_jsx(ImageIcon, { size: 16, className: "text-primary" }), _jsx("span", { className: "text-sm font-bold text-neutral-900", children: "Edit Image" })] }) })] }) })] }), showImagePicker && mounted && createPortal(_jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4", children: _jsxs("div", { className: "bg-white dark:bg-neutral-900 rounded-2xl w-full max-w-2xl p-6 shadow-2xl max-h-[90vh] overflow-y-auto", children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("h3", { className: "font-bold text-neutral-900 dark:text-white", children: "Hero Image Settings" }), _jsx("button", { onClick: () => setShowImagePicker(false), className: "p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors text-neutral-700 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-white", "aria-label": "Close", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "5", y1: "5", x2: "15", y2: "15" }), _jsx("line", { x1: "15", y1: "5", x2: "5", y2: "15" })] }) })] }), _jsx(ImagePicker, { value: imageId, onChange: (img) => {
50
+ updateField({ imageId: img === null || img === void 0 ? void 0 : img.id });
51
+ setTimeout(() => setShowImagePicker(false), 100);
52
+ }, showEffects: true, brightness: currentBrightness, blur: currentBlur, onBrightnessChange: (b) => { setCurrentBrightness(b); updateField({ brightness: b }); }, onBlurChange: (bl) => { setCurrentBlur(bl); updateField({ blur: bl }); } })] }) }), document.body)] }));
53
+ };
54
+ /**
55
+ * 3. Hero Preview Component
56
+ */
57
+ export const HeroPreview = ({ block, fallbackImage, context }) => {
58
+ const data = (block.data || {});
59
+ const contextFallback = context === null || context === void 0 ? void 0 : context.fallbackImage;
60
+ const effectiveFallback = fallbackImage || contextFallback;
61
+ // Logic: Use data.imageId, then fallback.id, then fallback.src
62
+ const activeImageId = data.imageId || (effectiveFallback === null || effectiveFallback === void 0 ? void 0 : effectiveFallback.id) || (effectiveFallback === null || effectiveFallback === void 0 ? void 0 : effectiveFallback.src);
63
+ const paragraphs = (data.summary || '').split('\n\n').filter(p => p.trim());
64
+ return (_jsx("section", { className: "relative pt-32 pb-20", children: _jsxs("div", { className: "max-w-7xl mx-auto px-6 lg:px-12 grid lg:grid-cols-2 gap-16 items-center", children: [_jsxs("div", { className: "order-2 lg:order-1", children: [_jsx("span", { className: "inline-flex px-4 py-1.5 rounded-full border border-sage/20 bg-sage/5 text-sage text-xs font-bold uppercase tracking-widest mb-8", children: data.category || 'Plant Care' }), _jsx("h1", { className: "text-5xl lg:text-7xl font-serif text-forest mb-8 leading-[1.1]", children: data.title || 'Zonder Titel' }), paragraphs.map((p, i) => (_jsx("p", { className: "text-lg lg:text-xl text-gray-600 font-light mt-4", children: p }, i)))] }), _jsx("div", { className: "order-1 lg:order-2 w-full", children: _jsx("div", { className: "relative w-full aspect-[4/5] lg:aspect-[3/4] rounded-3xl overflow-hidden shadow-2xl bg-neutral-100", children: activeImageId && (_jsx(PluginImage, { id: activeImageId, alt: data.title || 'Hero', fill: true, sizes: "(max-width: 1024px) 100vw, 50vw", className: "object-cover w-full h-full", editable: false })) }) })] }) }));
65
+ };
66
+ export const heroBlock = {
67
+ type: 'hero',
68
+ name: 'Hero Sectie',
69
+ description: 'De Botanics split-view introductie',
70
+ icon: ImageIcon,
71
+ category: 'layout',
72
+ defaultData: {
73
+ title: '',
74
+ category: '',
75
+ summary: '',
76
+ imageId: undefined,
77
+ },
78
+ validate: (data) => typeof data.title === 'string',
79
+ components: {
80
+ Edit: HeroEdit,
81
+ Preview: HeroPreview,
82
+ Icon: ImageIcon,
83
+ },
84
+ };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { BlockEditProps, BlockPreviewProps, ClientBlockDefinition } from '@jhits/plugin-blog';
3
+ export declare const ImageEdit: React.FC<BlockEditProps>;
4
+ export declare const ImagePreview: React.FC<BlockPreviewProps>;
5
+ export declare const imageBlock: ClientBlockDefinition;
6
+ //# sourceMappingURL=Image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../../src/plugin-blog/blocks/Image.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAGjF,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AA6B9F,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAoY9C,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA2BpD,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,qBAoBxB,CAAC"}