npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@theposeidonas/reservation-calendar

v0.2.0

Published

A framework-agnostic reservation calendar built for wedding photographers. Month / week / day views, package color indicators, fixed service grid, and payment tracking.

Readme

reservationCalendar

A React reservation calendar built for wedding photographers. Month / week / day views, package color indicators, a 12-service grid, and payment tracking.

npm version license


Features

  • Three views — month grid, week timegrid, day timegrid
  • Visual package indicators — color stripe or fill tint, plus a letter badge for colorblind accessibility
  • 12-service grid — fixed-position slots so users learn the layout; each slot optionally accepts a custom SVG icon
  • Smart month overflow — max N reservations per cell; the rest collapse into a day-list modal
  • Hairdresser time — separate timestamp shown in the reservation detail modal
  • Payment tracking — paid / remaining amounts with a progress bar
  • Light + dark theme out of the box, overridable via CSS custom properties
  • Heroicons by default — override any service slot with a raw SVG string
  • ~9 KB gzipped (no runtime dependencies beyond React ≥ 17)

Installation

npm install @theposeidonas/reservation-calendar
yarn add @theposeidonas/reservation-calendar
pnpm add @theposeidonas/reservation-calendar

Quick start

import ReservationCalendar from '@theposeidonas/reservation-calendar';
import '@theposeidonas/reservation-calendar/dist/style.css';

const packages = {
  gold:       { id: 'gold',       label: 'Gold',       color: '#d4a557', short: 'G' },
  platinum:   { id: 'platinum',   label: 'Platinum',   color: '#3a3a44', short: 'P' },
  engagement: { id: 'engagement', label: 'Söz / Nişan', color: '#c97296', short: 'N' },
};

const services = [
  { id: 'wedding',  label: 'Düğün' },
  { id: 'engage',   label: 'Söz / Nişan' },
  { id: 'savedate', label: 'Save the Date' },
  { id: 'after',    label: 'After Wedding' },
  { id: 'outdoor',  label: 'Dış Çekim' },
  { id: 'video',    label: 'Video' },
  { id: 'drone',    label: 'Drone' },
  { id: 'clip',     label: 'Klip' },
  { id: 'album',    label: 'Albüm' },
  { id: 'studio',   label: 'Stüdyo' },
  { id: 'booth',    label: 'Foto Kabin' },
  { id: 'express',  label: 'Hızlı Teslim' },
];

const reservations = [
  {
    id: 'r1',
    title: 'Burcu & Baran',
    start: new Date('2026-05-15T14:00'),
    end:   new Date('2026-05-15T18:00'),
    packageId: 'gold',
    hairdresserAt: new Date('2026-05-15T10:00'),
    venue: 'Conrad Bosphorus',
    phone: '+90 555 555 55 55',
    services: [true, false, true, false, true, true, false, false, true, true, false, false],
    totalPrice: 20000,
    paidAmount: 8000,
    notes: 'Hazırlık 2 saat önce başlayacak.',
  },
];

export default function App() {
  return (
    <ReservationCalendar
      reservations={reservations}
      packages={packages}
      services={services}
    />
  );
}

Props

| Prop | Type | Default | Description | |---|---|---|---| | reservations | Reservation[] | [] | List of reservation objects | | packages | Record<string, Package> | built-in | Package definitions keyed by id | | services | Service[] | built-in | Ordered list of service slots (max 12) | | variant | 'stripe' \| 'fill' | 'fill' | Chip / event card visual style | | badge | 'letter' \| 'dot' \| 'none' | 'letter' | Package indicator shown on chips | | maxPerCell | number | 3 | Max reservations shown per month cell before overflow | | defaultDark | boolean | false | Start in dark mode | | dayStart | number | 9 | First hour shown in week/day timegrid | | dayEnd | number | 21 | Last hour shown in week/day timegrid |


Data shapes

Reservation

| Field | Type | Required | Description | |---|---|---|---| | id | string | ✓ | Unique identifier | | title | string | ✓ | Short label, e.g. "Burcu & Baran" | | start | Date | ✓ | Start datetime | | end | Date | ✓ | End datetime | | packageId | string | ✓ | Key into your packages map | | services | boolean[] | | Activation flags for each service slot (index-aligned with the services prop) | | hairdresserAt | Date | | Hairdresser arrival time; shown as a pill in the detail modal | | venue | string | | Venue name | | phone | string | | Primary phone number | | phone2 | string | | Secondary phone number | | guests | number | | Guest count | | totalPrice | number | | Total price in the configured currency | | paidAmount | number | | Amount already paid | | notes | string | | Free-text notes; displayed as a pre-formatted block |

Package

{
  id: string;      // must match the key used in reservations
  label: string;   // e.g. "Gold"
  color: string;   // any CSS color value
  short: string;   // 1-2 character badge label
}

Service

{
  id: string;       // unique identifier
  label: string;    // tooltip text; leave empty for an unused placeholder slot
  icon?: string;    // optional raw SVG string — overrides the default Heroicon
}

Service icons

By default every slot renders a fixed Heroicons outline icon. Pass an icon field on any service object to replace it with your own SVG:

const services = [
  { id: 'wedding',  label: 'Düğün' },                               // → default Heroicon
  { id: 'custom',   label: 'Custom', icon: '<svg>...</svg>' },      // → your SVG
  { id: 'unused',   label: '' },                                    // → placeholder, no icon rendered
];

Icon requirements for best results:

  • Set fill="currentColor" or stroke="currentColor" so the icon inherits the slot's active/inactive color
  • Omit fixed width/height attributes; the component sizes the icon via CSS (60% of the slot in the grid, 12 px in the day-list row)
  • Pass a sanitized string — the component renders it with dangerouslySetInnerHTML and does not perform its own sanitization

From a PHP/Filament backend using blade-ui-kit/blade-icons:

use BladeUI\Icons\Factory as IconFactory;

$services = [
    ['id' => 'wedding', 'label' => 'Düğün',   'icon' => svg('heroicon-o-heart')->toHtml()],
    ['id' => 'video',   'label' => 'Video',    'icon' => svg('heroicon-o-video-camera')->toHtml()],
    ['id' => 'drone',   'label' => 'Drone',    'icon' => svg('heroicon-o-paper-airplane')->toHtml()],
    // ...remaining slots
];

Filament + Livewire

Feed reservations from your Eloquent model and forward clicks back to Livewire:

// app/Filament/Widgets/ReservationCalendarWidget.php
class ReservationCalendarWidget extends Widget
{
    protected string $view = 'filament.widgets.reservation-calendar';
    protected int|string|array $columnSpan = 'full';

    public function getViewData(): array
    {
        return [
            'reservations' => Reservation::query()
                ->where('tenant_id', Filament::getTenant()->id)
                ->with(['package'])
                ->get()
                ->map(fn ($r) => [
                    'id'            => (string) $r->id,
                    'title'         => $r->couple_name,
                    'start'         => $r->start_at->toIso8601String(),
                    'end'           => $r->end_at->toIso8601String(),
                    'packageId'     => $r->package->slug,
                    'hairdresserAt' => $r->hairdresser_at?->toIso8601String(),
                    'venue'         => $r->venue,
                    'phone'         => $r->bride_phone,
                    'phone2'        => $r->groom_phone,
                    'services'      => $r->getServiceFlags(),
                    'totalPrice'    => $r->total_price,
                    'paidAmount'    => $r->paid_amount,
                    'notes'         => $r->notes,
                ])
                ->all(),
        ];
    }
}
{{-- resources/views/filament/widgets/reservation-calendar.blade.php --}}
<x-filament-widgets::widget>
    <div
        wire:ignore
        x-data
        x-init="
            const el = $refs.rc;
            el.reservations = @js($reservations);
            el.packages      = @js(config('rc.packages'));
            el.services      = @js(config('rc.services'));
        "
    >
        <div x-ref="rc"></div>

        {{-- Mount the React component into $refs.rc via your JS bundle --}}
    </div>
</x-filament-widgets::widget>

Tip: Mount ReservationCalendar into the x-ref div from your JS bundle with ReactDOM.createRoot. Pass reservations, packages, and services as props that Alpine sets on the element — or keep them in a Livewire-aware Alpine store and re-render on $wire events.


Theming

Every color and dimension is exposed as a CSS custom property. Override on :root or scoped to the container:

.my-calendar-wrapper {
  --rc-bg: #fdfbf6;
  --rc-surface: #ffffff;
  --rc-text: #1f1c18;
  --rc-font: 'Manrope', system-ui, sans-serif;
  --rc-radius-lg: 18px;
}

Core tokens

| Token | Default (light) | Description | |---|---|---| | --rc-bg | #faf9f7 | Page background | | --rc-surface | #ffffff | Card / modal surface | | --rc-surface-2 | #f5f4f1 | Recessed surface (chips, notes) | | --rc-border | rgba(20,18,14,.08) | Default border | | --rc-text | #1a1815 | Primary text | | --rc-text-2 | #5e5a52 | Secondary text | | --rc-text-3 | #908b81 | Tertiary / muted text | | --rc-font | 'DM Sans', system-ui | UI font stack | | --rc-font-mono | 'JetBrains Mono', monospace | Monospace font stack | | --rc-radius-lg | 14px | Modal / card corner radius | | --rc-radius-sm | 6px | Chip / badge corner radius |

Dark mode is activated by setting data-rc-theme="dark" on <html> (the component handles this automatically when the user toggles the theme button).


Development

# Install dependencies
npm install

# Run the demo locally (Vite dev server)
npm run dev

# Build the library
npm run build

# Build + publish to npm
npm run release

Changelog

0.2.0

  • Custom service icons — each Service object now accepts an optional icon field (raw SVG string). When present it replaces the default Heroicon in both the 12-slot grid and the day-list mini icons. Empty-label placeholder slots render no icon.
  • Modal scroll fix — the reservation detail modal no longer clips service-slot tooltips. Scrolling is now handled by an inner rc-modal-scroll wrapper; the outer rc-modal stays overflow: visible so position: absolute tooltips can escape the boundary at any z-index.

0.1.1

  • Initial public release

License

MIT © Baran Arda


Links