Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const config: Config = {
{to: '/blog', label: 'Blog', position: 'left'},
{to: '/research', label: 'Research', position: 'left'},
{to: '/community', label: 'Community', position: 'left'},
{to: '/events', label: 'Events', position: 'left'},
{
type: 'docsVersionDropdown',
position: 'right',
Expand Down
92 changes: 92 additions & 0 deletions src/components/Events/events.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export interface Event {
title: string;
date: string;
endDate?: string;
location: string;
description: string;
link?: string;
type: "conference" | "workshop" | "meetup" | "webinar" | "hackathon" | "tutorial";
isUpcoming: boolean;
isExternal?: boolean; // true if link goes to external site
}

// Add your events here
// Events are automatically sorted by date
export const events: Event[] = [
// Upcoming Events
{
title: "ReCPS: Workshop on Reactive Cyber-Physical Systems",
date: "2026-04-20",
endDate: "2026-04-22",
location: "Verona, Italy (DATE 2026 Conference)",
description:
"Workshop on Reactive Cyber-Physical Systems: Design, Simulation, and Coordination, co-located with the Design, Automation and Test in Europe (DATE) Conference 2026.",
link: "/events/recps-2026",
type: "workshop",
isUpcoming: true,
},
// Past Events
{
title: "TCRS '25: Time-Centric Reactive Software",
date: "2025-10-02",
location: "Taipei, Taiwan (ESWEEK 2025)",
description:
"Third edition of the workshop on Time-Centric Reactive Software, co-located with Embedded Systems Week (ESWEEK) 2025 at the Taipei International Convention Center.",
link: "https://www.tcrs.io/",
type: "workshop",
isUpcoming: false,
isExternal: true,
},
{
title: "TCRS '24: Time-Centric Reactive Software",
date: "2024-10-03",
location: "Raleigh, NC, USA (ESWEEK 2024)",
description:
"Second edition of the workshop on Time-Centric Reactive Software, co-located with Embedded Systems Week (ESWEEK) 2024.",
link: "https://www.tcrs.io/2024/",
type: "workshop",
isUpcoming: false,
isExternal: true,
},
{
title: "TCRS '23: Time-Centric Reactive Software",
date: "2023-05-09",
location: "San Antonio, Texas (CPS-IoT Week 2023)",
description:
"First edition of the workshop on Time-Centric Reactive Software, co-located with ACM/IEEE CPS-IoT Week 2023.",
link: "https://www.tcrs.io/2023/",
type: "workshop",
isUpcoming: false,
isExternal: true,
},
{
title: "Lingua Franca Tutorial at ESWEEK 2021",
date: "2021-10-08",
location: "Online (EMSOFT Conference)",
description:
"A comprehensive tutorial introducing Lingua Franca, a polyglot coordination language for concurrent and time-sensitive applications. Part of the Embedded Systems Week (ESWEEK) 2021.",
link: "/events/esweek-2021-tutorial",
type: "tutorial",
isUpcoming: false,
},
];

// Helper to sort events by date
export const sortEventsByDate = (eventList: Event[], ascending = true): Event[] => {
return [...eventList].sort((a, b) => {
const dateA = new Date(a.date).getTime();
const dateB = new Date(b.date).getTime();
return ascending ? dateA - dateB : dateB - dateA;
});
};

export const upcomingEvents = sortEventsByDate(
events.filter((e) => e.isUpcoming),
true
);

export const pastEvents = sortEventsByDate(
events.filter((e) => !e.isUpcoming),
false
);

231 changes: 231 additions & 0 deletions src/components/Events/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import clsx from "clsx";

import Translate from "@docusaurus/Translate";
import Layout from "@theme/Layout";
import Heading from "@theme/Heading";
import Link from "@docusaurus/Link";

import { Event, upcomingEvents, pastEvents } from "./events";
import styles from "./styles.module.css";

// Calendar icon
const CalendarIcon = () => (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
);

// Location pin icon
const LocationIcon = () => (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
<circle cx="12" cy="10" r="3" />
</svg>
);

// Empty calendar icon
const EmptyCalendarIcon = () => (
<svg
width="64"
height="64"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
);

const formatDate = (dateStr: string, endDateStr?: string): string => {
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "long",
day: "numeric",
};
const startDate = new Date(dateStr).toLocaleDateString("en-US", options);

if (endDateStr) {
const endDate = new Date(endDateStr).toLocaleDateString("en-US", options);
return `${startDate} - ${endDate}`;
}

return startDate;
};

// External link icon
const ExternalLinkIcon = () => (
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={{ marginLeft: "4px", verticalAlign: "middle" }}
>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
<polyline points="15 3 21 3 21 9" />
<line x1="10" y1="14" x2="21" y2="3" />
</svg>
);

const EventCard = ({ event }: { event: Event }) => {
const typeClassName = styles[event.type] || "";
const linkProps = event.isExternal
? { target: "_blank", rel: "noopener noreferrer" }
: {};

return (
<div className={clsx("card", "margin-bottom--md", styles.eventCard)}>
<div className="card__header">
<div className="row">
<div className="col">
<span className={clsx(styles.eventType, typeClassName)}>
{event.type}
</span>
<Heading as="h3" className="margin-top--sm margin-bottom--none">
{event.link ? (
<Link href={event.link} {...linkProps}>
{event.title}
{event.isExternal && <ExternalLinkIcon />}
</Link>
) : (
event.title
)}
</Heading>
</div>
</div>
</div>
<div className="card__body">
<p>{event.description}</p>
<div className={styles.eventMeta}>
<span>
<CalendarIcon /> {formatDate(event.date, event.endDate)}
</span>
<span>
<LocationIcon /> {event.location}
</span>
</div>
</div>
{event.link && (
<div className="card__footer">
<Link
className="button button--primary button--sm"
href={event.link}
{...linkProps}
>
<Translate>{event.isExternal ? "Visit Website" : "Learn More"}</Translate>
{event.isExternal && <ExternalLinkIcon />}
</Link>
</div>
)}
</div>
);
};

const EmptyState = ({ message }: { message: string }) => (
<div className={styles.emptyState}>
<EmptyCalendarIcon />
<p>{message}</p>
</div>
);

export default function Events(): JSX.Element {
return (
<Layout
title="Events"
description="Lingua Franca events, workshops, and conferences"
>
{/* Hero Section */}
<div className={styles.heroSection}>
<div className="container">
<Heading as="h1" className={styles.heroTitle}>
<Translate>Events</Translate>
</Heading>
<p className={styles.heroSubtitle}>
<Translate>
Join us at conferences, workshops, and tutorials to learn more about
Lingua Franca and connect with the community.
</Translate>
</p>
</div>
</div>

{/* Upcoming Events */}
<div className="section">
<div className="container">
<Heading
as="h2"
className={clsx("margin-bottom--lg", "text--center")}
>
<Translate>Upcoming Events</Translate>
</Heading>
{upcomingEvents.length > 0 ? (
<div className="row">
<div className="col col--8 col--offset-2">
{upcomingEvents.map((event, idx) => (
<EventCard key={idx} event={event} />
))}
</div>
</div>
) : (
<EmptyState message="No upcoming events scheduled. Check back soon or follow us on Zulip for announcements!" />
)}
</div>
</div>

{/* Past Events */}
<div className="section sectionAlt">
<div className="container">
<Heading
as="h2"
className={clsx("margin-bottom--lg", "text--center")}
>
<Translate>Past Events</Translate>
</Heading>
{pastEvents.length > 0 ? (
<div className="row">
<div className="col col--8 col--offset-2">
{pastEvents.map((event, idx) => (
<EventCard key={idx} event={event} />
))}
</div>
</div>
) : (
<EmptyState message="No past events to display yet." />
)}
</div>
</div>
</Layout>
);
}

Loading