PHPackages                             blackpig-creatif/ephemeride - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. blackpig-creatif/ephemeride

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

blackpig-creatif/ephemeride
===========================

A Livewire calendar component for displaying events — part of the BlackpigCreatif package ecosystem

v1.1.0(2mo ago)07↓81.8%MITPHPPHP ^8.4

Since Apr 1Pushed 2mo agoCompare

[ Source](https://github.com/Blackpig/ephemeride)[ Packagist](https://packagist.org/packages/blackpig-creatif/ephemeride)[ RSS](/packages/blackpig-creatif-ephemeride/feed)WikiDiscussions main Synced 4w ago

READMEChangelog (2)Dependencies (22)Versions (3)Used By (0)

Éphéméride
==========

[](#éphéméride)

[![Latest Version on Packagist](https://camo.githubusercontent.com/89fc6b2dc1838543458cfdde2dc1276aa5e627d419bcdf600d31d59e7628c6e3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f626c61636b7069672d637265617469662f657068656d65726964652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/blackpig-creatif/ephemeride)[![Total Downloads](https://camo.githubusercontent.com/ce2e9029dafa2f253f1ba53f73355c750be8763f858e9f58e61363ae6f8fde44/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f626c61636b7069672d637265617469662f657068656d65726964652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/blackpig-creatif/ephemeride)

A Livewire calendar component for displaying events in month and week views, with RRULE recurrence, multi-day spanning banners, and CSS custom property theming.

Éphéméride is deliberately data-agnostic. You implement a single interface to supply events as `EphemerisEvent` DTOs — the component handles all layout geometry, all-day spanning, overflow, and navigation. No Eloquent models, no migrations, no Filament dependency.

Requirements
------------

[](#requirements)

- PHP 8.4+
- Laravel 12+
- Livewire 4+ (Alpine.js is bundled)

Installation
------------

[](#installation)

```
composer require blackpig-creatif/ephemeride
```

Publish the config (optional):

```
php artisan vendor:publish --tag="ephemeride-config"
```

Publish the CSS (optional — include in your build pipeline if you prefer the source):

```
php artisan vendor:publish --tag="ephemeride-assets"
```

---

Quick Start
-----------

[](#quick-start)

### 1. Generate an event provider

[](#1-generate-an-event-provider)

```
php artisan ephemeride:make-provider Festivals
```

This creates `app/BlackpigCreatif/Ephemeride/FestivalsProvider.php` with the interface already implemented and a commented example. The `Provider` suffix is added automatically — passing `FestivalsProvider` and `Festivals` both produce the same class name.

Open the generated file and implement `getEphemerides()`. The `$from`/`$to` window covers the full visible grid range, including leading and trailing days from adjacent months or weeks, so your query should use it as-is.

```
namespace App\BlackpigCreatif\Ephemeride;

use BlackpigCreatif\Ephemeride\Contracts\ProvidesEphemerides;
use BlackpigCreatif\Ephemeride\Data\EphemerisEvent;
use Carbon\Carbon;
use Illuminate\Support\Collection;

class FestivalsProvider implements ProvidesEphemerides
{
    public function getEphemerides(Carbon $from, Carbon $to): Collection
    {
        return Event::query()
            ->whereBetween('starts_at', [$from, $to])
            ->get()
            ->map(fn (Event $event) => EphemerisEvent::make(
                id:       (string) $event->id,
                title:    $event->title,
                startsAt: $event->starts_at,
                endsAt:   $event->ends_at,
                colour:   $event->colour,
                url:      route('events.show', $event),
            ));
    }
}
```

### 2. Mount the component

[](#2-mount-the-component)

```

```

### 3. Include the styles

[](#3-include-the-styles)

Pull `ephemeride.css` into your build pipeline:

```
// vite.config.js or app.css
import '/vendor/blackpig-creatif/ephemeride/resources/css/ephemeride.css'
```

Or reference the published file at `public/vendor/ephemeride/ephemeride.css`.

---

EphemerisEvent
--------------

[](#ephemerisevent)

`EphemerisEvent` is a `final` readonly-property DTO. Construct instances via the static factory:

```
EphemerisEvent::make(
    id:          'evt-123',
    title:       'Team Stand-up',
    startsAt:    Carbon::parse('2026-03-10 09:00'),
    endsAt:      Carbon::parse('2026-03-10 09:30'),
    url:         'https://example.com/events/123',  // optional — popover CTA and panel fallback
    description: 'Daily sync.',                      // optional — shown in popover and panel body
    colour:      'oklch(0.55 0.2 160)',             // optional — overrides theme event colour
    category:    'Internal',                         // optional — used for filtering
    rrule:       'FREQ=WEEKLY;BYDAY=MO,WE,FR',      // optional — RFC 5545 RRULE string
    imageUrl:    'https://example.com/thumb.jpg',    // optional — shown in popover and panel
    links:       [                                   // optional — multiple CTAs for the panel
        ['label' => 'Book Now', 'url' => 'https://example.com/book/123', 'style' => 'primary'],
        ['label' => 'More Info', 'url' => 'https://example.com/events/123', 'style' => 'secondary'],
    ],
);
```

### Properties

[](#properties)

PropertyTypeDescription`id``string`Unique identifier. Recurring occurrences append `-{YYYY-MM-DD}` automatically.`title``string`Displayed in chips and the popover header.`startsAt``Carbon`Inclusive start. For all-day events, set to midnight (`00:00`).`endsAt``Carbon`Inclusive end. For all-day events, set to midnight of the last day.`url``?string`Activates the CTA button in the event popover.`description``?string`Shown in the popover body.`imageUrl``?string`Thumbnail shown in the popover.`colour``?string`Any valid CSS colour value. Overrides `--ephemeride-color-event` for this event.`category``?string`Used by the optional category filter.`rrule``?string`RFC 5545 RRULE string (omit the `RRULE:` prefix).`exdates``array`Dates to exclude from a recurring series.`rdates``array`Additional one-off dates to include in a series.`extraAttributes``array`Pass-through data for consuming code or custom Blade slots.`links``array`Ordered CTA links for the event panel (see [Panel Mode](#panel-mode)).### Computed property hooks

[](#computed-property-hooks)

Read-only virtual properties — no storage, computed on access:

PropertyTypeValue`durationInMinutes``int``endsAt->diffInMinutes(startsAt)``formattedTimeRange``string`e.g. `'09:00 – 10:30'``isAllDay``bool``true` when both `startsAt` and `endsAt` are at midnight### All-day events

[](#all-day-events)

Set both `startsAt` and `endsAt` to midnight (`00:00`). Multi-day all-day events — where `endsAt > startsAt` — render as horizontal spanning banners in both month and week views. The `endsAt` date is **inclusive**: an event from Mar 10 to Mar 12 covers all three days.

```
EphemerisEvent::make(
    id:       'retreat',
    title:    'Spring Retreat',
    startsAt: Carbon::parse('2026-03-10 00:00'),
    endsAt:   Carbon::parse('2026-03-12 00:00'),
);
```

---

Recurring Events
----------------

[](#recurring-events)

Pass an RFC 5545 RRULE string to the `rrule` property. Éphéméride uses `rlanvin/php-rrule` internally — omit the `RRULE:` prefix.

```
EphemerisEvent::make(
    id:       'standup',
    title:    'Daily Stand-up',
    startsAt: Carbon::parse('2026-01-01 09:00'),
    endsAt:   Carbon::parse('2026-01-01 09:30'),
    rrule:    'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',
);
```

Exclusions and additional dates are supported via `exdates` and `rdates`:

```
EphemerisEvent::make(
    id:       'standup',
    title:    'Daily Stand-up',
    startsAt: Carbon::parse('2026-01-01 09:00'),
    endsAt:   Carbon::parse('2026-01-01 09:30'),
    rrule:    'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',
    exdates:  [new \DateTime('2026-03-17')],
);
```

Expansion is bounded to the current grid window (not just the calendar month), so recurring events that fall on leading or trailing days from adjacent months are included correctly.

---

Views
-----

[](#views)

### Month view

[](#month-view)

A 42-cell (6×7) grid padded to six rows for layout stability. Multi-day all-day events render as horizontal spanning banners above the day cells for each week row. Per-day timed events stack up to `month_max_events_per_day` chips; any overflow shows a `+X more` label.

### Week view

[](#week-view)

A CSS Grid layout (not a ``). Day columns are aligned to the calendar week; rows represent time slots at the configured interval. Timed events are positioned and sized relative to `week_start_hour`. All-day events occupy a pinned row above the time grid and span multiple day columns where appropriate. Times outside `week_start_hour`–`week_end_hour` are not rendered.

### Controlling available views

[](#controlling-available-views)

```
{{-- Both views, starting with month (default) --}}

{{-- Month only — view switcher is hidden automatically --}}

{{-- Start in week view --}}

```

---

Theming
-------

[](#theming)

All colours, radii, and font references in the Blade templates use CSS custom properties. There are no hardcoded colour values.

### CSS overrides

[](#css-overrides)

Override the root tokens in your own stylesheet after importing the package CSS:

```
:root {
    --ephemeride-color-primary:  oklch(0.55 0.2 160);
    --ephemeride-color-event:    oklch(0.55 0.2 160);
    --ephemeride-color-today-bg: oklch(0.55 0.2 160);
    --ephemeride-radius:         0.75rem;
}
```

### Config-level defaults

[](#config-level-defaults)

Values in `config/ephemeride.php` under `theme` are applied as inline custom properties to every component instance:

```
'theme' => [
    'color-primary'  => 'oklch(0.55 0.2 160)',
    'color-event'    => 'oklch(0.55 0.2 160)',
    'color-today-bg' => 'oklch(0.55 0.2 160)',
    'radius'         => '0.5rem',
],
```

### Per-instance overrides

[](#per-instance-overrides)

The `:theme` prop applies scoped custom properties to the component's root element, overriding config defaults for that instance only:

```

```

### Available tokens

[](#available-tokens)

TokenDefaultDescription`--ephemeride-color-primary``oklch(0.55 0.2 270)`Navigation buttons, active states`--ephemeride-color-surface``oklch(1 0 0)`Day cell and grid background`--ephemeride-color-surface-alt``oklch(0.97 0 0)`Out-of-month day cells`--ephemeride-color-border``oklch(0.9 0 0)`Grid lines and dividers`--ephemeride-color-text``oklch(0.15 0 0)`Primary text`--ephemeride-color-text-muted``oklch(0.55 0 0)`Day numbers, time labels`--ephemeride-color-today-bg``oklch(0.55 0.2 270)`Today date number circle background`--ephemeride-color-today-text``oklch(1 0 0)`Today date number text`--ephemeride-color-event``oklch(0.55 0.2 270)`Default event chip background`--ephemeride-color-popover-bg``oklch(1 0 0)`Event popover background`--ephemeride-radius``0.5rem`Border radius for chips, popovers, buttons---

Category Filtering
------------------

[](#category-filtering)

Enable the filter bar by passing `:filterable="true"`. Categories are derived automatically from the `category` property on the events in the current grid window — no configuration needed.

```

```

Clicking a category chip filters the rendered events to that category; clicking again clears the filter. The active category resets when the view period changes.

---

Event Popover
-------------

[](#event-popover)

By default, clicking an event chip opens an inline popover card anchored to the chip. No configuration is required.

The popover shows the event title, date and time, category badge, image (when `imageUrl` is set), a truncated description, and a single CTA link (when `url` is set and `popover_cta_label` is configured).

To customise the popover layout, publish the views and edit `resources/views/vendor/ephemeride/components/event-popover.blade.php`.

---

Panel Mode
----------

[](#panel-mode)

Panel mode moves the event detail display out of the inline popover and into a separate DOM element that you place anywhere on the page. This suits layouts where the detail sits to the side of, above, or below the calendar rather than floating over it.

When `target-container` is set to any non-empty string on the calendar component, clicking an event chip dispatches a browser `CustomEvent` named `ephemeride-event-selected` to `window` instead of opening a popover. Any element on the page that listens for this event can render the detail.

### Step 1 -- Configure the calendar

[](#step-1----configure-the-calendar)

```

```

### Step 2 -- Place the panel component

[](#step-2----place-the-panel-component)

`` is the default receiver. Put it wherever you want the detail to appear. It listens globally for `ephemeride-event-selected` and renders the selected event automatically.

```

```

The panel is invisible when nothing is selected but always occupies layout space to prevent shift. It fades in when an event is clicked. A close button (SVG icon) clears the selection.

### Multiple CTAs in the panel

[](#multiple-ctas-in-the-panel)

When the event has a `links` array the panel renders each link as a styled button instead of the single `url` CTA. Each link has three keys:

KeyRequiredDescription`label`yesButton text`url`yesLink destination`style`no`primary` (default), `secondary`, or `ghost````
EphemerisEvent::make(
    id: 'workshop-1',
    title: 'Yin and Restore Afternoon',
    startsAt: Carbon::parse('2026-04-26 14:00'),
    endsAt: Carbon::parse('2026-04-26 17:00'),
    links: [
        ['label' => 'Book Now', 'url' => 'https://example.com/book/1', 'style' => 'primary'],
        ['label' => 'More Info', 'url' => 'https://example.com/info/1', 'style' => 'secondary'],
    ],
)
```

If `links` is empty the panel falls back to rendering the single `url` field as a primary CTA, keeping backwards compatibility with any existing providers.

### Custom panel template

[](#custom-panel-template)

To use your own markup, listen for the `ephemeride-event-selected` event directly. The `event.detail` object contains the full event payload:

KeyTypeDescription`id`string`title`string`startsAt`stringISO 8601`endsAt`stringISO 8601`isAllDay`bool`formattedDate`stringPre-formatted, e.g. `"26 April 2026 · 14:00 -- 17:00"``url`string or nullSingle CTA URL`links`arrayOrdered CTA links`description`string or null`imageUrl`string or null`category`string or null`colour`string or nullCSS colour string`extraAttributes`objectAny extra data from the provider```

        {{-- Multiple CTAs --}}

        {{-- Single URL fallback --}}

            View Details

```

### Panel min-height

[](#panel-min-height)

The outer panel container always reserves layout space. Override the default (10rem) for your layout:

```
.ephemeride-panel {
    --ephemeride-panel-min-height: 14rem;
}
```

---

Configuration Reference
-----------------------

[](#configuration-reference)

Published to `config/ephemeride.php`:

```
return [
    // Views available in the switcher. Supported: 'month', 'week'.
    'views'                    => ['month', 'week'],

    // The view shown on first render.
    'default_view'             => 'month',

    // Hour range for the week view time grid. Events outside this window are not shown.
    'week_start_hour'          => 7,
    'week_end_hour'            => 21,

    // Slot interval in minutes.
    'slot_interval'            => 30,

    // First day of the week. 1 = Monday (ISO), 0 = Sunday.
    'week_starts_at'           => 1,

    // Maximum event chips per day cell before '+X more' is shown.
    'month_max_events_per_day' => 3,

    // Label for the CTA button in the popover and as the panel single-url fallback.
    // Has no effect when the event provides a `links` array.
    'popover_cta_label'        => 'View Details',

    // Global CSS custom property overrides. Merged with package defaults.
    // Per-instance :theme prop values take precedence over these.
    'theme'                    => [],
];
```

---

Architecture
------------

[](#architecture)

The component is thin at the boundary. It resolves the provider class via `app($provider)`, calls `getEphemerides($from, $to)` with the full grid window, expands recurring events through `EventExpander` (using `RSet` to handle RRULE, EXDATE, and RDATE in a single pass), then passes the flat collection to `MonthGrid` or `WeekGrid` for placement geometry. The Livewire component class holds no event data; everything is derived on each render.

Three namespacing systems are registered independently:

SystemTagLivewire component``Internal view namespace`ephemeride::livewire.calendar`Anonymous Blade components`` etc.Provider classes are resolved through the container, so constructor injection works as expected:

```
namespace App\BlackpigCreatif\Ephemeride;

class FestivalsProvider implements ProvidesEphemerides
{
    public function __construct(private readonly EventRepository $repository) {}

    public function getEphemerides(Carbon $from, Carbon $to): Collection
    {
        return $this->repository->forWindow($from, $to)->map(/* ... */);
    }
}
```

If the provider requires explicit binding:

```
$this->app->bind(FestivalsProvider::class, fn () => new FestivalsProvider(
    app(EventRepository::class),
));
```

---

Artisan Commands
----------------

[](#artisan-commands)

### Make Provider

[](#make-provider)

```
php artisan ephemeride:make-provider Festivals
```

Generates `app/BlackpigCreatif/Ephemeride/FestivalsProvider.php` — a scaffolded class implementing `ProvidesEphemerides` with a commented example body.

The `Provider` suffix is appended automatically and deduplicated, so all of the following produce `FestivalsProvider`:

```
php artisan ephemeride:make-provider Festivals
php artisan ephemeride:make-provider FestivalsProvider
```

If no name is given the command will prompt for one:

```
php artisan ephemeride:make-provider
# What should the provider be called? (e.g., Festivals, Events, Retreats)
```

---

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

See [CHANGELOG](CHANGELOG.md).

Credits
-------

[](#credits)

- [Stuart Hallewell](https://github.com/blackpig-creatif)
- [All Contributors](../../contributors)

License
-------

[](#license)

MIT. See [LICENSE](LICENSE.md).

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance85

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~13 days

Total

2

Last Release

77d ago

PHP version history (2 changes)v1.0.0PHP ^8.3

v1.1.0PHP ^8.4

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1029317?v=4)[Blackpig Créatif](/maintainers/Blackpig)[@Blackpig](https://github.com/Blackpig)

---

Top Contributors

[![Blackpig](https://avatars.githubusercontent.com/u/1029317?v=4)](https://github.com/Blackpig "Blackpig (7 commits)")

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/blackpig-creatif-ephemeride/health.svg)

```
[![Health](https://phpackages.com/badges/blackpig-creatif-ephemeride/health.svg)](https://phpackages.com/packages/blackpig-creatif-ephemeride)
```

###  Alternatives

[statamic/cms

The Statamic CMS Core Package

4.8k3.6M932](/packages/statamic-cms)[nasirkhan/laravel-starter

A CMS like modular Laravel starter project.

1.4k2.7k](/packages/nasirkhan-laravel-starter)[team-nifty-gmbh/tall-datatables

Server-side rendered datatables for Laravel and Livewire

1319.7k3](/packages/team-nifty-gmbh-tall-datatables)[tomshaw/electricgrid

A feature-rich Livewire package designed for projects that require dynamic, interactive data tables.

119.2k](/packages/tomshaw-electricgrid)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
