@levo-so/blocks 0.3.13 → 0.3.14

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@levo-so/blocks",
3
3
  "description": "Set of blocks for Levo studio",
4
- "version": "0.3.13",
4
+ "version": "0.3.14",
5
5
  "author": "Levo Engineering <devs@theinternetfolks.com>",
6
6
  "dependencies": {
7
7
  "clsx": "2.1.1",
@@ -11,13 +11,13 @@
11
11
  "use-debounce": "10.0.2"
12
12
  },
13
13
  "devDependencies": {
14
+ "@levo-so/core": "0.3.14",
15
+ "@levo-so/react": "0.3.14",
16
+ "@levo-so/studio": "0.3.14",
17
+ "@levo/ts-config": "0.0.0",
14
18
  "@types/react": "19.2.14",
15
19
  "@types/react-dom": "19.2.3",
16
- "typescript": "5.9.3",
17
- "@levo-so/core": "0.3.13",
18
- "@levo-so/react": "0.3.13",
19
- "@levo-so/studio": "0.3.13",
20
- "@levo/ts-config": "0.0.0"
20
+ "typescript": "5.9.3"
21
21
  },
22
22
  "exports": {
23
23
  ".": "./src/index.ts"
@@ -28,11 +28,11 @@
28
28
  ],
29
29
  "main": "./src/index.ts",
30
30
  "peerDependencies": {
31
+ "@levo-so/core": "0.3.14",
32
+ "@levo-so/react": "0.3.14",
33
+ "@levo-so/studio": "0.3.14",
31
34
  "react": ">=18",
32
- "react-dom": ">=18",
33
- "@levo-so/react": "0.3.13",
34
- "@levo-so/core": "0.3.13",
35
- "@levo-so/studio": "0.3.13"
35
+ "react-dom": ">=18"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
@@ -14,12 +14,12 @@ const EVENT_BASE = {
14
14
  const EVENTS = [
15
15
  {
16
16
  ...EVENT_BASE,
17
- event_starts_date: "2026-05-12T04:00:00.000Z",
18
- event_starts_month: "2026-05-12T04:00:00.000Z",
19
- event_ends_date: "2026-05-13T04:00:00.000Z",
20
- event_ends_month: "2026-05-13T04:00:00.000Z",
21
- starts_at: "2026-05-12T04:00:00.000Z",
22
- ends_at: "2026-05-13T23:59:00.000Z",
17
+ event_starts_date: "2026-09-12T04:00:00.000Z",
18
+ event_starts_month: "2026-09-12T04:00:00.000Z",
19
+ event_ends_date: "2026-09-13T04:00:00.000Z",
20
+ event_ends_month: "2026-09-13T04:00:00.000Z",
21
+ starts_at: "2026-09-12T04:00:00.000Z",
22
+ ends_at: "2026-09-13T23:59:00.000Z",
23
23
  event_category: "Compliance",
24
24
  event_title: "PCI DSS 4.0 Readiness Workshop",
25
25
  event_description:
@@ -27,12 +27,12 @@ const EVENTS = [
27
27
  },
28
28
  {
29
29
  ...EVENT_BASE,
30
- event_starts_date: "2026-05-19T04:30:00.000Z",
31
- event_starts_month: "2026-05-19T04:30:00.000Z",
30
+ event_starts_date: "2026-09-19T04:30:00.000Z",
31
+ event_starts_month: "2026-09-19T04:30:00.000Z",
32
32
  event_ends_date: "",
33
33
  event_ends_month: "",
34
- starts_at: "2026-05-19T04:30:00.000Z",
35
- ends_at: "2026-05-19T12:00:00.000Z",
34
+ starts_at: "2026-09-19T04:30:00.000Z",
35
+ ends_at: "2026-09-19T12:00:00.000Z",
36
36
  event_category: "Payments",
37
37
  event_title: "Cross-Border Payments Summit",
38
38
  event_description:
@@ -40,12 +40,12 @@ const EVENTS = [
40
40
  },
41
41
  {
42
42
  ...EVENT_BASE,
43
- event_starts_date: "2026-05-27T04:00:00.000Z",
44
- event_starts_month: "2026-05-27T04:00:00.000Z",
45
- event_ends_date: "2026-05-28T04:00:00.000Z",
46
- event_ends_month: "2026-05-28T04:00:00.000Z",
47
- starts_at: "2026-05-27T04:00:00.000Z",
48
- ends_at: "2026-05-28T23:59:00.000Z",
43
+ event_starts_date: "2026-09-27T04:00:00.000Z",
44
+ event_starts_month: "2026-09-27T04:00:00.000Z",
45
+ event_ends_date: "2026-09-28T04:00:00.000Z",
46
+ event_ends_month: "2026-09-28T04:00:00.000Z",
47
+ starts_at: "2026-09-27T04:00:00.000Z",
48
+ ends_at: "2026-09-28T23:59:00.000Z",
49
49
  event_category: "Settlement",
50
50
  event_title: "Real-Time Settlement Forum",
51
51
  event_description:
@@ -53,12 +53,12 @@ const EVENTS = [
53
53
  },
54
54
  {
55
55
  ...EVENT_BASE,
56
- event_starts_date: "2026-06-04T08:30:00.000Z",
57
- event_starts_month: "2026-06-04T08:30:00.000Z",
56
+ event_starts_date: "2026-10-04T08:30:00.000Z",
57
+ event_starts_month: "2026-10-04T08:30:00.000Z",
58
58
  event_ends_date: "",
59
59
  event_ends_month: "",
60
- starts_at: "2026-06-04T08:30:00.000Z",
61
- ends_at: "2026-06-04T12:00:00.000Z",
60
+ starts_at: "2026-10-04T08:30:00.000Z",
61
+ ends_at: "2026-10-04T12:00:00.000Z",
62
62
  event_category: "Identity",
63
63
  event_title: "KYC & Identity Roundtable",
64
64
  event_description:
@@ -66,12 +66,12 @@ const EVENTS = [
66
66
  },
67
67
  {
68
68
  ...EVENT_BASE,
69
- event_starts_date: "2026-06-11T04:00:00.000Z",
70
- event_starts_month: "2026-06-11T04:00:00.000Z",
71
- event_ends_date: "2026-06-12T04:00:00.000Z",
72
- event_ends_month: "2026-06-12T04:00:00.000Z",
73
- starts_at: "2026-06-11T04:00:00.000Z",
74
- ends_at: "2026-06-12T23:59:00.000Z",
69
+ event_starts_date: "2026-10-11T04:00:00.000Z",
70
+ event_starts_month: "2026-10-11T04:00:00.000Z",
71
+ event_ends_date: "2026-10-12T04:00:00.000Z",
72
+ event_ends_month: "2026-10-12T04:00:00.000Z",
73
+ starts_at: "2026-10-11T04:00:00.000Z",
74
+ ends_at: "2026-10-12T23:59:00.000Z",
75
75
  event_category: "Engineering",
76
76
  event_title: "Open Banking DevDay",
77
77
  event_description:
@@ -79,12 +79,12 @@ const EVENTS = [
79
79
  },
80
80
  {
81
81
  ...EVENT_BASE,
82
- event_starts_date: "2026-06-18T04:00:00.000Z",
83
- event_starts_month: "2026-06-18T04:00:00.000Z",
82
+ event_starts_date: "2026-11-18T04:00:00.000Z",
83
+ event_starts_month: "2026-11-18T04:00:00.000Z",
84
84
  event_ends_date: "",
85
85
  event_ends_month: "",
86
- starts_at: "2026-06-18T04:00:00.000Z",
87
- ends_at: "2026-06-18T11:00:00.000Z",
86
+ starts_at: "2026-11-18T04:00:00.000Z",
87
+ ends_at: "2026-11-18T11:00:00.000Z",
88
88
  event_category: "Treasury",
89
89
  event_title: "Treasury & FinOps Bootcamp",
90
90
  event_description:
@@ -229,6 +229,11 @@ export const Calendar1: IBlock = {
229
229
  label: "Event Starts Month",
230
230
  field_interface: "TypographyWidget",
231
231
  },
232
+ {
233
+ key: "event_starts_year",
234
+ label: "Event Starts Year",
235
+ field_interface: "TypographyWidget",
236
+ },
232
237
  {
233
238
  key: "event_to",
234
239
  label: "Event To",
@@ -254,6 +259,11 @@ export const Calendar1: IBlock = {
254
259
  label: "Event Ends Month",
255
260
  field_interface: "TypographyWidget",
256
261
  },
262
+ {
263
+ key: "event_ends_year",
264
+ label: "Event Ends Year",
265
+ field_interface: "TypographyWidget",
266
+ },
257
267
  {
258
268
  key: "divider",
259
269
  label: "Divider",
@@ -274,6 +284,16 @@ export const Calendar1: IBlock = {
274
284
  label: "Event Sub Content",
275
285
  field_interface: "BoxWidget",
276
286
  },
287
+ {
288
+ key: "event_title_wrapper",
289
+ label: "Event Title Wrapper",
290
+ field_interface: "BoxWidget",
291
+ },
292
+ {
293
+ key: "event_status_pill",
294
+ label: "Event Status Pill",
295
+ field_interface: "TypographyWidget",
296
+ },
277
297
  {
278
298
  key: "event_title",
279
299
  label: "Event Title",
@@ -460,6 +480,11 @@ export const Calendar1: IBlock = {
460
480
  "text-align": "center",
461
481
  "font-weight": 600,
462
482
  },
483
+ event_starts_year: {
484
+ "text-align": "center",
485
+ "font-size": "xs",
486
+ opacity: 0.75,
487
+ },
463
488
  event_ends_date: {
464
489
  "text-align": "center",
465
490
  },
@@ -467,6 +492,11 @@ export const Calendar1: IBlock = {
467
492
  "text-align": "center",
468
493
  "font-weight": 600,
469
494
  },
495
+ event_ends_year: {
496
+ "text-align": "center",
497
+ "font-size": "xs",
498
+ opacity: 0.75,
499
+ },
470
500
  divider: {
471
501
  "margin-left": "2xs",
472
502
  "margin-right": "3xl",
@@ -501,6 +531,23 @@ export const Calendar1: IBlock = {
501
531
  "flex-direction": "column",
502
532
  "row-gap": "2xs",
503
533
  },
534
+ event_title_wrapper: {
535
+ display: "flex",
536
+ "align-items": "center",
537
+ "column-gap": "xs",
538
+ "flex-wrap": "wrap",
539
+ },
540
+ event_status_pill: {
541
+ display: "inline-flex",
542
+ "align-items": "center",
543
+ "padding-top": "2px",
544
+ "padding-bottom": "2px",
545
+ "padding-left": "sm",
546
+ "padding-right": "sm",
547
+ "border-radius": "full",
548
+ "font-size": "xs",
549
+ "font-weight": 600,
550
+ },
504
551
  event_ends: {
505
552
  display: "flex",
506
553
  "flex-direction": "column",
@@ -11,46 +11,67 @@ import {
11
11
  useContentEngine,
12
12
  } from "@levo-so/studio";
13
13
  import dayjs from "dayjs";
14
- import { useState } from "react";
14
+ import { useEffect, useState } from "react";
15
15
 
16
16
  import type { ICalendar1Content } from "./calendar-1.schema";
17
17
 
18
+ type IEventItem = ICalendar1Content["listing_section"][number] & {
19
+ starts_at?: string;
20
+ ends_at?: string;
21
+ };
22
+
18
23
  const EventCard: React.FC<{
19
- event: ICalendar1Content["listing_section"][number];
24
+ event: IEventItem;
20
25
  index: number;
21
26
  }> = ({ event, index }) => {
22
- const isEndDateValid = !!(
23
- dayjs(event?.event_ends_date).isValid() && dayjs(event?.event_ends_month).isValid()
27
+ const startDate = dayjs(
28
+ event?.event_starts_date && dayjs(event.event_starts_date).isValid()
29
+ ? event.event_starts_date
30
+ : event?.starts_at,
24
31
  );
25
-
26
- const isStartDateIsSameAsEndDate = dayjs(event?.event_starts_date).isSame(
27
- event?.event_ends_date,
28
- "day",
32
+ const endDate = dayjs(
33
+ event?.event_ends_date && dayjs(event.event_ends_date).isValid()
34
+ ? event.event_ends_date
35
+ : event?.ends_at,
29
36
  );
30
37
 
38
+ const isStartDateValid = startDate.isValid();
39
+ const isEndDateValid = endDate.isValid() && !startDate.isSame(endDate, "day");
40
+
31
41
  const isStartTimeValid = dayjs(event?.starts_at).isValid();
32
42
  const isEndTimeValid = dayjs(event?.ends_at).isValid();
33
43
 
44
+ const today = dayjs();
45
+ const effectiveEnd = endDate.isValid() ? endDate : startDate;
46
+ const isCompleted = effectiveEnd.isValid() && effectiveEnd.isBefore(today, "day");
47
+ const statusText = isCompleted ? "Completed" : "Upcoming";
48
+
34
49
  return (
35
50
  <Box elementKey={`listing_section.${index}.event`}>
36
51
  <Box elementKey={`listing_section.${index}.event_timing`}>
37
52
  <Box elementKey={`listing_section.${index}.event_starts`}>
38
53
  <Heading elementKey={`listing_section.${index}.event_starts_date`}>
39
- {dayjs(event?.event_starts_date).format("DD")}
54
+ {isStartDateValid ? startDate.format("DD") : ""}
40
55
  </Heading>
41
56
  <Typography elementKey={`listing_section.${index}.event_starts_month`}>
42
- {dayjs(event?.event_starts_date).format("MMMM")}
57
+ {isStartDateValid ? startDate.format("MMMM") : ""}
58
+ </Typography>
59
+ <Typography elementKey={`listing_section.${index}.event_starts_year`}>
60
+ {isStartDateValid ? startDate.format("YYYY") : ""}
43
61
  </Typography>
44
62
  </Box>
45
- {isEndDateValid && !isStartDateIsSameAsEndDate ? (
63
+ {isEndDateValid ? (
46
64
  <>
47
65
  <Typography elementKey={`listing_section.${index}.event_to`} />
48
66
  <Box elementKey={`listing_section.${index}.event_ends`}>
49
67
  <Heading elementKey={`listing_section.${index}.event_ends_date`}>
50
- {dayjs(event?.event_ends_date).format("DD")}
68
+ {endDate.format("DD")}
51
69
  </Heading>
52
70
  <Typography elementKey={`listing_section.${index}.event_ends_month`}>
53
- {dayjs(event?.event_ends_month).format("MMMM")}
71
+ {endDate.format("MMMM")}
72
+ </Typography>
73
+ <Typography elementKey={`listing_section.${index}.event_ends_year`}>
74
+ {endDate.format("YYYY")}
54
75
  </Typography>
55
76
  </Box>
56
77
  </>
@@ -72,10 +93,41 @@ const EventCard: React.FC<{
72
93
  </Box>
73
94
  <Box elementKey={`listing_section.${index}.divider`} />
74
95
  <Box elementKey={`listing_section.${index}.event_content`}>
75
- <Heading elementKey={`listing_section.${index}.event_category`} />
96
+ <Heading elementKey={`listing_section.${index}.event_category`}>
97
+ {event?.event_category}
98
+ </Heading>
76
99
  <Box elementKey={`listing_section.${index}.event_sub_content`}>
77
- <Heading elementKey={`listing_section.${index}.event_title`} />
78
- <Typography elementKey={`listing_section.${index}.event_description`} />
100
+ <Box elementKey={`listing_section.${index}.event_title_wrapper`}>
101
+ <Typography
102
+ elementKey={`listing_section.${index}.event_status_pill`}
103
+ style={{
104
+ display: "inline-flex",
105
+ alignItems: "center",
106
+ padding: "2px 8px",
107
+ borderRadius: "9999px",
108
+ fontSize: "11px",
109
+ fontWeight: 600,
110
+ lineHeight: "1.2",
111
+ backgroundColor: isCompleted
112
+ ? "var(--color-muted, oklch(0.94 0 0))"
113
+ : "var(--color-success-subtle, oklch(0.95 0.05 145))",
114
+ color: isCompleted
115
+ ? "var(--color-muted-foreground, oklch(0.42 0 0))"
116
+ : "var(--color-success, oklch(0.38 0.12 145))",
117
+ border: isCompleted
118
+ ? "1px solid oklch(0.88 0 0)"
119
+ : "1px solid oklch(0.85 0.08 145)",
120
+ }}
121
+ >
122
+ {statusText}
123
+ </Typography>
124
+ <Heading elementKey={`listing_section.${index}.event_title`}>
125
+ {event?.event_title}
126
+ </Heading>
127
+ </Box>
128
+ <Typography elementKey={`listing_section.${index}.event_description`}>
129
+ {event?.event_description}
130
+ </Typography>
79
131
  </Box>
80
132
  </Box>
81
133
  </Box>
@@ -83,41 +135,106 @@ const EventCard: React.FC<{
83
135
  };
84
136
 
85
137
  const Calendar1: React.FC<ILevoBlockBaseProps<ICalendar1Content>> = ({ content }) => {
86
- const { data, query, setQuery } = useContentEngine("listing_section");
138
+ const { data, query, setQuery, loading } = useContentEngine("listing_section");
87
139
 
88
140
  const [calendarMonth, setCalendarMonth] = useState<Date>(new Date());
141
+ const [selectedMonth, setSelectedMonth] = useState<Date | null>(null);
142
+ const [isPendingMonthChange, setIsPendingMonthChange] = useState(false);
143
+
144
+ useEffect(() => {
145
+ if (!loading) {
146
+ setIsPendingMonthChange(false);
147
+ }
148
+ }, [loading]);
149
+
150
+ const rawEvents: IEventItem[] = !selectedMonth
151
+ ? (content?.listing_section ?? [])
152
+ : data && data.length > 0
153
+ ? data
154
+ : (content?.listing_section ?? []);
155
+
156
+ const getEventStart = (item: IEventItem) => {
157
+ const d1 = item?.event_starts_date ? dayjs(item.event_starts_date) : null;
158
+ if (d1 && d1.isValid()) return d1;
159
+ const d2 = item?.starts_at ? dayjs(item.starts_at) : null;
160
+ if (d2 && d2.isValid()) return d2;
161
+ return null;
162
+ };
163
+
164
+ const getEventEnd = (item: IEventItem, start: dayjs.Dayjs | null) => {
165
+ const d1 = item?.event_ends_date ? dayjs(item.event_ends_date) : null;
166
+ if (d1 && d1.isValid()) return d1;
167
+ const d2 = item?.ends_at ? dayjs(item.ends_at) : null;
168
+ if (d2 && d2.isValid()) return d2;
169
+ return start;
170
+ };
171
+
172
+ // Sort events chronologically ascending by start date
173
+ const sortedEvents = [...rawEvents].sort((a, b) => {
174
+ const startA = getEventStart(a);
175
+ const startB = getEventStart(b);
176
+ const dateA = startA ? startA.valueOf() : 0;
177
+ const dateB = startB ? startB.valueOf() : 0;
178
+ return dateA - dateB;
179
+ });
180
+
181
+ const currentMonthStart = dayjs().startOf("month");
182
+
183
+ // Filter displayed events: events matching selectedMonth if user picked a month,
184
+ // or events from current month onwards (current month or future) on initial view / refresh
185
+ const upcomingOrSelectedEvents = sortedEvents.filter((item) => {
186
+ const start = getEventStart(item);
187
+ const end = getEventEnd(item, start);
188
+ if (!start) return false;
189
+
190
+ if (!selectedMonth) {
191
+ const effectiveEnd = end || start;
192
+ return (
193
+ effectiveEnd.isAfter(currentMonthStart) || effectiveEnd.isSame(currentMonthStart, "day")
194
+ );
195
+ }
196
+
197
+ const monthStart = dayjs(selectedMonth).startOf("month");
198
+ const monthEnd = dayjs(selectedMonth).endOf("month");
199
+ const effectiveEnd = end || start;
200
+
201
+ return (
202
+ (start.isBefore(monthEnd) || start.isSame(monthEnd)) &&
203
+ (effectiveEnd.isAfter(monthStart) || effectiveEnd.isSame(monthStart))
204
+ );
205
+ });
206
+
207
+ // If no upcoming events exist from current month onwards, fall back to showing all sorted events
208
+ const displayedEvents =
209
+ !selectedMonth && upcomingOrSelectedEvents.length === 0
210
+ ? sortedEvents
211
+ : upcomingOrSelectedEvents;
89
212
 
90
- const dateList = data
91
- ?.map((v: any) => v?.event_starts_date)
92
- ?.filter((v: any) => dayjs(v)?.isValid())
93
- ?.map((v: any) => new Date(v));
213
+ const dateList = rawEvents
214
+ ?.map((v) => getEventStart(v))
215
+ ?.filter((v): v is dayjs.Dayjs => v !== null && v.isValid())
216
+ ?.map((v) => v.toDate());
94
217
 
95
218
  const handleMonthChange = (date: Date) => {
219
+ setIsPendingMonthChange(true);
96
220
  setCalendarMonth(date);
221
+ setSelectedMonth(date);
97
222
  setQuery({
98
- ...query,
99
223
  limit: -1,
100
224
  where: {
101
- ...(query?.where ?? {}),
102
- AND: [
103
- {
104
- starts_at: {
105
- gte: dayjs(date).startOf("month").format(),
106
- },
107
- },
108
- {
109
- ends_at: {
110
- lte: dayjs(date).endOf("month").format(),
111
- },
112
- },
113
- ],
225
+ starts_at: {
226
+ gte: dayjs(date).startOf("month").toISOString(),
227
+ lte: dayjs(date).endOf("month").toISOString(),
228
+ },
114
229
  },
115
230
  });
116
231
  };
117
232
 
118
233
  const handleResetFilter = () => {
234
+ setIsPendingMonthChange(false);
235
+ setSelectedMonth(null);
236
+ setCalendarMonth(new Date());
119
237
  setQuery({
120
- ...query,
121
238
  limit: 20,
122
239
  where: {},
123
240
  });
@@ -132,11 +249,11 @@ const Calendar1: React.FC<ILevoBlockBaseProps<ICalendar1Content>> = ({ content }
132
249
  </Box>
133
250
  <Box elementKey="content">
134
251
  <Box elementKey="listing_section_levoGroup" data-levo_group>
135
- {data && data.length > 0 ? (
136
- data?.map((item: any, index: number) => {
252
+ {displayedEvents && displayedEvents.length > 0 ? (
253
+ displayedEvents.map((item: IEventItem, index: number) => {
137
254
  return <EventCard key={`listing_section.${index}`} index={index} event={item} />;
138
255
  })
139
- ) : (
256
+ ) : loading || isPendingMonthChange ? null : (
140
257
  <Box elementKey="empty_state">
141
258
  <Icon elementKey="empty_state_icon" />
142
259
  <Heading elementKey="empty_state_title" />
@@ -162,11 +279,12 @@ const Calendar1: React.FC<ILevoBlockBaseProps<ICalendar1Content>> = ({ content }
162
279
  />
163
280
  </Box>
164
281
  <Box elementKey="calendar_ctas_levoGroup" data-levo_group>
165
- {(content.calendar_ctas ?? []).map((_: any, index: number) => (
282
+ {(content.calendar_ctas ?? []).map((_: unknown, index: number) => (
166
283
  <Button
167
284
  key={`calendar_ctas.${index}.calendar_cta`}
168
285
  elementKey={`calendar_ctas.${index}.calendar_cta`}
169
286
  data-levo_group_item
287
+ onClick={handleResetFilter}
170
288
  />
171
289
  ))}
172
290
  <Button elementKey="empty_state_cta" onClick={handleResetFilter} />
@@ -3,8 +3,7 @@ import type { IBlock } from "@levo-so/studio";
3
3
  const HERO_IMAGE = {
4
4
  id: "7453352427682930486",
5
5
  kind: "image",
6
- location:
7
- "https://images.pexels.com/photos/5686124/pexels-photo-5686124.jpeg",
6
+ location: "https://images.pexels.com/photos/5686124/pexels-photo-5686124.jpeg",
8
7
  mimetype: "image/jpeg",
9
8
  srcset: {
10
9
  "320w":
@@ -150,14 +149,14 @@ const DEFAULT_CONTENT = {
150
149
  },
151
150
  ],
152
151
 
152
+ // Sidebar Embed
153
+ sidebar_embed:
154
+ '<div class="sidebar-video-container"><iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" title="YouTube Video 1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe><iframe src="https://www.youtube.com/embed/L_LUpnjgPso" title="YouTube Video 2" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe><iframe src="https://www.youtube.com/embed/3JZ_D3ELwOQ" title="YouTube Video 3" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div><style>.sidebar-video-container { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 100%; box-sizing: border-box; overflow: hidden; } .sidebar-video-container iframe { width: 100% !important; max-width: 100% !important; aspect-ratio: 16 / 9; border: 0; border-radius: 12px; box-sizing: border-box; display: block; }</style>',
155
+
153
156
  // Social section
154
157
  social_section: null,
155
158
  social_title: "Share this role",
156
- social_icons: [
157
- { icon: ICON_LINKEDIN },
158
- { icon: ICON_TWITTER },
159
- { icon: ICON_FACEBOOK },
160
- ],
159
+ social_icons: [{ icon: ICON_LINKEDIN }, { icon: ICON_TWITTER }, { icon: ICON_FACEBOOK }],
161
160
 
162
161
  // Job content
163
162
  job_content: null,
@@ -331,8 +330,7 @@ export const Jobs1: IBlock = {
331
330
  label: "Hero Image (Mobile)",
332
331
  field_interface: "MediaWidget",
333
332
  hint: {
334
- prompt_description:
335
- "Optimized version of the hero image for mobile screens.",
333
+ prompt_description: "Optimized version of the hero image for mobile screens.",
336
334
  orientation: "portrait",
337
335
  size: "medium",
338
336
  usecase: "hero",
@@ -363,8 +361,7 @@ export const Jobs1: IBlock = {
363
361
  label: "Details Content Wrapper",
364
362
  field_interface: "BoxWidget",
365
363
  hint: {
366
- prompt_description:
367
- "Main wrapper for the two-column layout of job details.",
364
+ prompt_description: "Main wrapper for the two-column layout of job details.",
368
365
  },
369
366
  },
370
367
 
@@ -374,8 +371,7 @@ export const Jobs1: IBlock = {
374
371
  label: "Sidebar",
375
372
  field_interface: "BoxWidget",
376
373
  hint: {
377
- prompt_description:
378
- "Left sidebar container for job info cards and social sharing.",
374
+ prompt_description: "Left sidebar container for job info cards and social sharing.",
379
375
  },
380
376
  },
381
377
  {
@@ -410,8 +406,7 @@ export const Jobs1: IBlock = {
410
406
  field_interface: "BoxWidget",
411
407
  kind: "group",
412
408
  hint: {
413
- prompt_description:
414
- "Repeatable label-value pairs for job information.",
409
+ prompt_description: "Repeatable label-value pairs for job information.",
415
410
  },
416
411
  fields: [
417
412
  {
@@ -436,8 +431,7 @@ export const Jobs1: IBlock = {
436
431
  label: "Value",
437
432
  field_interface: "TypographyWidget",
438
433
  hint: {
439
- prompt_description:
440
- "Value for the job attribute (e.g., Full-time, 3-5 years).",
434
+ prompt_description: "Value for the job attribute (e.g., Full-time, 3-5 years).",
441
435
  usecase: "description",
442
436
  min_characters: 3,
443
437
  max_characters: 50,
@@ -450,8 +444,7 @@ export const Jobs1: IBlock = {
450
444
  label: "Card Button",
451
445
  field_interface: "ButtonWidget",
452
446
  hint: {
453
- prompt_description:
454
- "Call-to-action button for the card (e.g., Apply Now).",
447
+ prompt_description: "Call-to-action button for the card (e.g., Apply Now).",
455
448
  usecase: "button",
456
449
  min_characters: 5,
457
450
  max_characters: 20,
@@ -460,6 +453,17 @@ export const Jobs1: IBlock = {
460
453
  ],
461
454
  },
462
455
 
456
+ // Sidebar Embed
457
+ {
458
+ key: "sidebar_embed",
459
+ label: "Sidebar Video Embed",
460
+ field_interface: "IframeWidget",
461
+ hint: {
462
+ prompt_description:
463
+ "Embedded video or custom HTML widget displayed in the left sidebar below job overview card.",
464
+ },
465
+ },
466
+
463
467
  // Social section
464
468
  {
465
469
  key: "social_section",
@@ -486,8 +490,7 @@ export const Jobs1: IBlock = {
486
490
  field_interface: "BoxWidget",
487
491
  kind: "group",
488
492
  hint: {
489
- prompt_description:
490
- "Social media platform icons for sharing the job listing.",
493
+ prompt_description: "Social media platform icons for sharing the job listing.",
491
494
  },
492
495
  fields: [
493
496
  {
@@ -510,8 +513,7 @@ export const Jobs1: IBlock = {
510
513
  label: "Job Content Area",
511
514
  field_interface: "BoxWidget",
512
515
  hint: {
513
- prompt_description:
514
- "Main job content area containing meta info and description.",
516
+ prompt_description: "Main job content area containing meta info and description.",
515
517
  },
516
518
  },
517
519
 
@@ -521,8 +523,7 @@ export const Jobs1: IBlock = {
521
523
  label: "Job Meta Information",
522
524
  field_interface: "BoxWidget",
523
525
  hint: {
524
- prompt_description:
525
- "Container for job metadata like department, location, and dates.",
526
+ prompt_description: "Container for job metadata like department, location, and dates.",
526
527
  },
527
528
  },
528
529
  {
@@ -570,8 +571,7 @@ export const Jobs1: IBlock = {
570
571
  label: "Meta Details",
571
572
  field_interface: "BoxWidget",
572
573
  hint: {
573
- prompt_description:
574
- "Container for posting date and application deadline.",
574
+ prompt_description: "Container for posting date and application deadline.",
575
575
  },
576
576
  },
577
577
  {
@@ -784,6 +784,7 @@ export const Jobs1: IBlock = {
784
784
  width: "100%",
785
785
  },
786
786
  "max-width": "256px",
787
+ "min-width": "0",
787
788
  },
788
789
  info_cards_levoGroup: {
789
790
  display: "flex",
@@ -839,6 +840,13 @@ export const Jobs1: IBlock = {
839
840
  items: [{}, {}, {}, {}],
840
841
  },
841
842
  ],
843
+ sidebar_embed: {
844
+ width: "100%",
845
+ "max-width": "100%",
846
+ "overflow-x": "hidden",
847
+ "overflow-y": "hidden",
848
+ height: "fit-content",
849
+ },
842
850
  social_section: {
843
851
  "padding-top": "xl",
844
852
  "border-top": "1px solid",
@@ -7,6 +7,7 @@ import {
7
7
  Form,
8
8
  Heading,
9
9
  Icon,
10
+ Iframe,
10
11
  Image,
11
12
  Section,
12
13
  Typography,
@@ -84,6 +85,8 @@ const Jobs1: React.FC<ILevoBlockBaseProps<IJobs1Content>> = ({ content }) => (
84
85
  </Box>
85
86
  ))}
86
87
  </Box>
88
+ {/* Sidebar Embed Widget */}
89
+ <Iframe elementKey="sidebar_embed" />
87
90
  {/* Social Media Icons */}
88
91
  <Box elementKey="social_section">
89
92
  <Heading elementKey="social_title" />