calendarkit-prov2 2.0.0 → 2.0.1

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/README.md CHANGED
@@ -78,20 +78,12 @@ import 'calendarkit-prov2/calendarkit.css';
78
78
  import { ProScheduler } from 'calendarkit-prov2';
79
79
  import type { CalendarEvent, ViewType } from 'calendarkit-prov2';
80
80
  import { useState } from 'react';
81
- import { fr } from 'date-fns/locale';
82
81
 
83
- export default function AdvancedCalendar() {
82
+ export default function MyCalendar() {
84
83
  const [events, setEvents] = useState<CalendarEvent[]>([]);
85
84
  const [view, setView] = useState<ViewType>('week');
86
85
  const [date, setDate] = useState(new Date());
87
86
  const [isDark, setIsDark] = useState(false);
88
- const [language, setLanguage] = useState<'en' | 'fr' | 'tr'>('en');
89
- const [timezone, setTimezone] = useState('America/New_York');
90
-
91
- const calendars = [
92
- { id: 'work', label: 'Work', color: '#3b82f6', active: true },
93
- { id: 'personal', label: 'Personal', color: '#10b981', active: true },
94
- ];
95
87
 
96
88
  return (
97
89
  <div className={isDark ? 'dark' : ''}>
@@ -102,12 +94,6 @@ export default function AdvancedCalendar() {
102
94
  onViewChange={setView}
103
95
  date={date}
104
96
  onDateChange={setDate}
105
- calendars={calendars}
106
- timezone={timezone}
107
- onTimezoneChange={setTimezone}
108
- language={language}
109
- onLanguageChange={setLanguage}
110
- locale={language === 'fr' ? fr : undefined}
111
97
  isDarkMode={isDark}
112
98
  onThemeToggle={() => setIsDark(!isDark)}
113
99
  onEventCreate={(event) => {
@@ -169,93 +155,161 @@ import type {
169
155
 
170
156
  ## Props
171
157
 
172
- | Prop | Type | Description |
173
- |------|------|-------------|
174
- | `events` | `CalendarEvent[]` | Events to display |
175
- | `view` | `ViewType` | `'month' \| 'week' \| 'day' \| 'agenda' \| 'resource'` |
176
- | `date` | `Date` | Focused date |
177
- | `onViewChange` | `(view) => void` | View change handler |
178
- | `onDateChange` | `(date) => void` | Date change handler |
179
- | `onEventCreate` | `(event) => void` | New event |
180
- | `onEventUpdate` | `(event) => void` | Update event |
181
- | `onEventDelete` | `(id) => void` | Delete event |
182
- | `onEventDrop` | `(event, start, end) => void` | Drag and drop handler |
183
- | `onEventResize` | `(event, start, end) => void` | Resize handler |
184
- | `calendars` | `Calendar[]` | Multi-calendar filter |
185
- | `resources` | `Resource[]` | Resource view columns |
186
- | `eventTypes` | `EventType[]` | Event type presets |
187
- | `timezone` | `string` | IANA timezone |
188
- | `onTimezoneChange` | `(tz) => void` | Timezone change |
189
- | `language` | `LanguageCode` | UI language (15+ locales, default `tr`) |
190
- | `onLanguageChange` | `(lang) => void` | Language change |
191
- | `locale` | `Locale` | date-fns locale |
192
- | `isDarkMode` | `boolean` | Dark mode state |
193
- | `onThemeToggle` | `() => void` | Theme toggle |
194
- | `theme` | `CalendarTheme` | Custom colors / radius |
195
- | `customViews` | `CustomView[]` | Extra header views |
196
- | `sidebarMenus` | `SidebarMenuItem[]` | Custom sidebar pages |
197
- | `readOnly` | `boolean` | Disable editing |
198
- | `isLoading` | `boolean` | Skeleton state |
199
- | `renderEventForm` | `(props) => ReactNode` | Custom event modal |
200
- | `renderHeader` | `(props) => ReactNode` | Custom header |
158
+ | Prop | Type | Default | Description |
159
+ |------|------|---------|-------------|
160
+ | `events` | `CalendarEvent[]` | `[]` | Events to display |
161
+ | `view` | `ViewType` | — | `'month' \| 'week' \| 'day' \| 'agenda' \| 'resource'` |
162
+ | `date` | `Date` | — | Focused date |
163
+ | `onViewChange` | `(view) => void` | — | View change handler |
164
+ | `onDateChange` | `(date) => void` | — | Date change handler |
165
+ | `onEventCreate` | `(event) => void` | — | New event handler |
166
+ | `onEventUpdate` | `(event) => void` | — | Update event handler |
167
+ | `onEventDelete` | `(id) => void` | — | Delete event handler |
168
+ | `onEventDrop` | `(event, start, end) => void` | — | Drag and drop handler |
169
+ | `onEventResize` | `(event, start, end) => void` | — | Resize handler |
170
+ | `calendars` | `Calendar[]` | — | Multi-calendar filter list |
171
+ | `resources` | `Resource[]` | — | Resource view columns |
172
+ | `eventTypes` | `EventType[]` | — | Event type presets |
173
+ | `timezone` | `string` | — | IANA timezone string |
174
+ | `onTimezoneChange` | `(tz) => void` | — | Timezone change handler |
175
+ | `language` | `LanguageCode` | `'tr'` | UI language (15+ locales) |
176
+ | `onLanguageChange` | `(lang) => void` | — | Language change handler |
177
+ | `hideLanguageSwitcher` | `boolean` | `false` | Hide the language dropdown from the header. The `language` prop still sets the active locale. |
178
+ | `locale` | `Locale` | — | date-fns locale for date formatting |
179
+ | `isDarkMode` | `boolean` | — | Dark mode state |
180
+ | `onThemeToggle` | `() => void` | — | Theme toggle handler |
181
+ | `theme` | `CalendarTheme` | — | Custom colors / border radius |
182
+ | `customViews` | `CustomView[]` | — | Extra views shown in the header switcher |
183
+ | `sidebarMenus` | `SidebarMenuItem[]` | — | Custom sidebar pages |
184
+ | `readOnly` | `boolean` | `false` | Disable all editing interactions |
185
+ | `isLoading` | `boolean` | `false` | Show skeleton loading state |
186
+ | `hideViewSwitcher` | `boolean` | `false` | Hide the view switcher buttons from the header |
187
+ | `initialScrollHour` | `number` | `8` | Hour (0–23) the Day/Week views scroll to on mount |
188
+ | `reverseTime` | `boolean` | `false` | Reverse the time axis (23:00 at top, 00:00 at bottom) |
189
+ | `renderEventForm` | `(props) => ReactNode` | — | Replace the default event modal with a custom component |
190
+ | `renderHeader` | `(props) => ReactNode` | — | Replace the entire header with a custom component |
191
+ | `renderMiniCalendar` | `(props) => ReactNode` | — | Replace the sidebar mini calendar |
192
+ | `renderEmptyState` | `(props) => ReactNode` | — | Custom empty state for Agenda view |
201
193
 
202
194
  ---
203
195
 
204
- ## Publish to npm (Access Token)
196
+ ## Header features
197
+
198
+ ### Event count badge
199
+
200
+ The header automatically displays a **live event count badge** next to the view switcher. The count reflects only the events visible in the current view period:
205
201
 
206
- Repository: [github.com/Sayzera/calendarkit-prov2](https://github.com/Sayzera/calendarkit-prov2)
202
+ | View | Counted range |
203
+ |------|--------------|
204
+ | Month | All events within the current month |
205
+ | Week | All events within the current week (Mon–Sun) |
206
+ | Day / Resource | Events on the current day |
207
+ | Agenda | Events in the next 30 days |
207
208
 
208
- npm publish requires an **Access Token** (for CI/CD or local automation). You do not need 2FA OTP on every publish when using a token with publish permission.
209
+ The badge updates automatically when you navigate or filter calendars.
209
210
 
210
- ### Step 1 — Create npm Access Token
211
+ ### Create Event button
211
212
 
212
- 1. Go to [npmjs.com](https://www.npmjs.com) → **Account** → **Access Tokens**
213
- 2. **Generate New Token** → **Granular Access Token**
214
- 3. Configure:
215
- - **Token name:** `calendarkit-prov2-publish`
216
- - **Expiration:** your choice (90 days / 1 year)
217
- - **Packages:** select `calendarkit-prov2` (or all packages you own)
218
- - **Permissions:** **Read and write**
219
- 4. Enable **Bypass 2FA for publish** if the option is shown (required for automated publish)
220
- 5. Copy the token (`npm_...`) — it is shown only once
213
+ The **Create Event** button in the header displays only the `+` icon. Hovering over it reveals a tooltip with the label. On mobile, a floating action button (FAB) is shown instead.
221
214
 
222
- ### Step 2 — Publish from your machine (PowerShell)
215
+ ### Language switcher
223
216
 
224
- ```powershell
225
- # Set token for this session only (do not commit the token)
226
- $env:NPM_TOKEN = "npm_YOUR_TOKEN_HERE"
217
+ Use `hideLanguageSwitcher` to lock the UI to a specific language without exposing the dropdown to end users:
227
218
 
228
- npm run build:lib
229
- npm publish --access public
219
+ ```tsx
220
+ <ProScheduler
221
+ language="tr"
222
+ hideLanguageSwitcher
223
+ ...
224
+ />
230
225
  ```
231
226
 
232
- The project `.npmrc` reads `NPM_TOKEN` from the environment automatically.
227
+ ---
228
+
229
+ ## CalendarEvent model
230
+
231
+ ```ts
232
+ interface CalendarEvent {
233
+ id: string;
234
+ title: string;
235
+ start: Date;
236
+ end: Date;
237
+ description?: string;
238
+ color?: string;
239
+ allDay?: boolean;
240
+ calendarId?: string;
241
+ resourceId?: string;
242
+ type?: string;
243
+ icon?: string;
244
+ attachments?: EventAttachment[];
245
+ guests?: string[];
246
+ reminders?: EventReminder[];
247
+ recurrence?: {
248
+ freq: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
249
+ interval?: number;
250
+ count?: number;
251
+ until?: Date;
252
+ byweekday?: number[]; // 0 = Monday, 6 = Sunday
253
+ };
254
+ }
255
+ ```
233
256
 
234
- ### Step 3 — Publish via GitHub Actions (recommended)
257
+ ---
235
258
 
236
- 1. GitHub repo → **Settings** → **Secrets and variables** → **Actions**
237
- 2. **New repository secret**
238
- - Name: `NPM_TOKEN`
239
- - Value: your `npm_...` token
240
- 3. Bump version and push a tag:
259
+ ## Multi-calendar filtering
241
260
 
242
- ```bash
243
- npm version patch
244
- git push origin main --tags
261
+ ```tsx
262
+ const calendars = [
263
+ { id: 'work', label: 'Work', color: '#3b82f6', active: true },
264
+ { id: 'personal', label: 'Personal', color: '#10b981', active: true },
265
+ ];
266
+
267
+ <ProScheduler
268
+ calendars={calendars}
269
+ onCalendarToggle={(id, active) => {
270
+ // update your calendar list
271
+ }}
272
+ ...
273
+ />
245
274
  ```
246
275
 
247
- Or run manually: **Actions** → **Publish to npm** → **Run workflow**
276
+ Events with a matching `calendarId` are shown/hidden based on the `active` flag. Events without a `calendarId` are always shown.
277
+
278
+ ---
279
+
280
+ ## Custom views
281
+
282
+ Add extra views to the header switcher:
283
+
284
+ ```tsx
285
+ const customViews: CustomView[] = [
286
+ {
287
+ id: 'timeline',
288
+ label: 'Timeline',
289
+ icon: <MyIcon />,
290
+ component: <MyTimelineView />,
291
+ },
292
+ ];
248
293
 
249
- The workflow file: `.github/workflows/publish-npm.yml`
294
+ <ProScheduler customViews={customViews} ... />
295
+ ```
296
+
297
+ ---
250
298
 
251
- ### What gets published
299
+ ## Custom sidebar menus
252
300
 
253
- `build:lib` produces:
301
+ ```tsx
302
+ const sidebarMenus: SidebarMenuItem[] = [
303
+ {
304
+ id: 'reports',
305
+ label: 'Reports',
306
+ icon: <BarChart2 />,
307
+ component: <ReportsPanel />,
308
+ },
309
+ ];
254
310
 
255
- - `dist/index.js` (CommonJS)
256
- - `dist/index.mjs` (ESM)
257
- - `dist/index.d.ts` / `dist/index.d.mts` (TypeScript types)
258
- - `dist/calendarkit.css` (theme tokens)
311
+ <ProScheduler sidebarMenus={sidebarMenus} ... />
312
+ ```
259
313
 
260
314
  ---
261
315
 
package/dist/index.d.mts CHANGED
@@ -249,6 +249,11 @@ interface CalendarProps {
249
249
  /** BCP 47 language code. Defaults to 'tr' (Turkish). */
250
250
  language?: LanguageCode;
251
251
  onLanguageChange?: (lang: LanguageCode) => void;
252
+ /**
253
+ * When true, the language-switcher dropdown is hidden from the header.
254
+ * The `language` prop still sets the active locale; it just won't be user-changeable.
255
+ */
256
+ hideLanguageSwitcher?: boolean;
252
257
  /**
253
258
  * Replace the default event form/modal with a fully custom component.
254
259
  * Receives open state, the event being edited, and save/delete handlers.
package/dist/index.d.ts CHANGED
@@ -249,6 +249,11 @@ interface CalendarProps {
249
249
  /** BCP 47 language code. Defaults to 'tr' (Turkish). */
250
250
  language?: LanguageCode;
251
251
  onLanguageChange?: (lang: LanguageCode) => void;
252
+ /**
253
+ * When true, the language-switcher dropdown is hidden from the header.
254
+ * The `language` prop still sets the active locale; it just won't be user-changeable.
255
+ */
256
+ hideLanguageSwitcher?: boolean;
252
257
  /**
253
258
  * Replace the default event form/modal with a fully custom component.
254
259
  * Receives open state, the event being edited, and save/delete handlers.
package/dist/index.js CHANGED
@@ -1515,7 +1515,9 @@ var CalendarHeader = ({
1515
1515
  onCreateEvent,
1516
1516
  customViews,
1517
1517
  activeCustomViewId,
1518
- onCustomViewChange
1518
+ onCustomViewChange,
1519
+ eventCount,
1520
+ hideLanguageSwitcher = false
1519
1521
  }) => {
1520
1522
  const [isLangOpen, setIsLangOpen] = React15.useState(false);
1521
1523
  const [isCustomViewOpen, setIsCustomViewOpen] = React15.useState(false);
@@ -1607,15 +1609,15 @@ var CalendarHeader = ({
1607
1609
  className: "h-9 px-5 rounded-xl text-sm font-medium hidden sm:inline-flex border-[0.5px] border-border/60 hover:border-primary/40 hover:bg-primary/5 hover:text-primary transition-all duration-200"
1608
1610
  },
1609
1611
  translations.today
1610
- ), onCreateEvent && /* @__PURE__ */ React15__namespace.default.createElement(
1612
+ ), onCreateEvent && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative group hidden sm:block" }, /* @__PURE__ */ React15__namespace.default.createElement(
1611
1613
  Button,
1612
1614
  {
1613
1615
  onClick: onCreateEvent,
1614
- className: "h-9 px-4 rounded-xl text-sm font-medium hidden sm:inline-flex gap-1.5 shadow-sm shadow-primary/20 transition-all duration-200"
1616
+ size: "icon",
1617
+ className: "h-9 w-9 rounded-xl shadow-sm shadow-primary/20 transition-all duration-200"
1615
1618
  },
1616
- /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Plus, { className: "h-4 w-4" }),
1617
- /* @__PURE__ */ React15__namespace.default.createElement("span", null, translations.createEvent ?? translations.create)
1618
- ), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center bg-muted/40 rounded-xl p-0.5" }, /* @__PURE__ */ React15__namespace.default.createElement(
1619
+ /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Plus, { className: "h-4 w-4" })
1620
+ ), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "pointer-events-none absolute left-1/2 -translate-x-1/2 top-full mt-2 z-50 opacity-0 group-hover:opacity-100 transition-opacity duration-150" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "bg-popover text-popover-foreground text-xs font-medium px-2.5 py-1.5 rounded-lg shadow-md border border-border/60 whitespace-nowrap" }, translations.createEvent ?? translations.create), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-popover border-l border-t border-border/60 rotate-45" }))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center bg-muted/40 rounded-xl p-0.5" }, /* @__PURE__ */ React15__namespace.default.createElement(
1619
1621
  Button,
1620
1622
  {
1621
1623
  variant: "ghost",
@@ -1633,7 +1635,7 @@ var CalendarHeader = ({
1633
1635
  className: "rounded-lg h-8 w-8 hover:bg-background/80 transition-all duration-200"
1634
1636
  },
1635
1637
  /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
1636
- )), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "ml-2 md:ml-4 hidden sm:block" }, /* @__PURE__ */ React15__namespace.default.createElement("h2", { className: "text-lg md:text-xl font-semibold text-foreground whitespace-nowrap capitalize tracking-tight" }, dateFns.format(currentDate, "MMMM yyyy", { locale })))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "sm:hidden" }, /* @__PURE__ */ React15__namespace.default.createElement("h2", { className: "text-lg font-semibold text-foreground whitespace-nowrap capitalize tracking-tight" }, dateFns.format(currentDate, "MMMM yyyy", { locale })))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 md:gap-3 w-full md:w-auto justify-end" }, onLanguageChange && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React15__namespace.default.createElement(
1638
+ )), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "ml-2 md:ml-4 hidden sm:block" }, /* @__PURE__ */ React15__namespace.default.createElement("h2", { className: "text-lg md:text-xl font-semibold text-foreground whitespace-nowrap capitalize tracking-tight" }, dateFns.format(currentDate, "MMMM yyyy", { locale })))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "sm:hidden" }, /* @__PURE__ */ React15__namespace.default.createElement("h2", { className: "text-lg font-semibold text-foreground whitespace-nowrap capitalize tracking-tight" }, dateFns.format(currentDate, "MMMM yyyy", { locale })))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 md:gap-3 w-full md:w-auto justify-end" }, onLanguageChange && !hideLanguageSwitcher && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React15__namespace.default.createElement(
1637
1639
  "button",
1638
1640
  {
1639
1641
  ref: langButtonRef,
@@ -1687,6 +1689,14 @@ var CalendarHeader = ({
1687
1689
  onClick: onThemeToggle
1688
1690
  },
1689
1691
  isDarkMode ? /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Sun, { className: "h-4 w-4 text-amber-500" }) : /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Moon, { className: "h-4 w-4 text-muted-foreground" })
1692
+ ), typeof eventCount === "number" && !hideViewSwitcher && /* @__PURE__ */ React15__namespace.default.createElement(
1693
+ "div",
1694
+ {
1695
+ className: "flex items-center gap-1.5 h-8 px-3 rounded-xl bg-primary/10 text-primary text-xs font-semibold tabular-nums select-none border border-primary/20 shadow-sm shadow-primary/10",
1696
+ title: `${eventCount} event${eventCount !== 1 ? "s" : ""}`
1697
+ },
1698
+ /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.CalendarCheck2, { className: "w-3.5 h-3.5 shrink-0" }),
1699
+ /* @__PURE__ */ React15__namespace.default.createElement("span", null, eventCount)
1690
1700
  ), !hideViewSwitcher && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center bg-muted/50 backdrop-blur-sm rounded-xl p-1 gap-0.5" }, viewConfig.map(({ key, icon: Icon }) => /* @__PURE__ */ React15__namespace.default.createElement(
1691
1701
  Button,
1692
1702
  {
@@ -4140,6 +4150,7 @@ var Scheduler = ({
4140
4150
  onThemeToggle,
4141
4151
  translations,
4142
4152
  hideViewSwitcher,
4153
+ hideLanguageSwitcher,
4143
4154
  initialScrollHour = 8,
4144
4155
  reverseTime: reverseTimeProp = false,
4145
4156
  language = "tr",
@@ -4290,6 +4301,34 @@ var Scheduler = ({
4290
4301
  return activeCalendarIds.includes(e.calendarId);
4291
4302
  });
4292
4303
  }, [expandedEvents, calendars]);
4304
+ const viewEventCount = React15.useMemo(() => {
4305
+ let rangeStart;
4306
+ let rangeEnd;
4307
+ switch (view) {
4308
+ case "month":
4309
+ rangeStart = dateFns.startOfMonth(currentDate);
4310
+ rangeEnd = dateFns.endOfMonth(currentDate);
4311
+ break;
4312
+ case "week":
4313
+ rangeStart = dateFns.startOfWeek(currentDate, { weekStartsOn: 1 });
4314
+ rangeEnd = dateFns.endOfWeek(currentDate, { weekStartsOn: 1 });
4315
+ break;
4316
+ case "day":
4317
+ case "resource":
4318
+ rangeStart = dateFns.startOfDay(currentDate);
4319
+ rangeEnd = dateFns.endOfDay(currentDate);
4320
+ break;
4321
+ case "agenda":
4322
+ rangeStart = dateFns.startOfDay(currentDate);
4323
+ rangeEnd = dateFns.addDays(rangeStart, 30);
4324
+ break;
4325
+ default:
4326
+ return filteredEvents.length;
4327
+ }
4328
+ return filteredEvents.filter(
4329
+ (e) => e.start >= rangeStart && e.start <= rangeEnd
4330
+ ).length;
4331
+ }, [filteredEvents, view, currentDate]);
4293
4332
  return /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, null, /* @__PURE__ */ React15__namespace.default.createElement(
4294
4333
  core.DndContext,
4295
4334
  {
@@ -4344,7 +4383,9 @@ var Scheduler = ({
4344
4383
  onCustomViewChange: (id2) => {
4345
4384
  setActiveCustomViewId(id2);
4346
4385
  setActiveCustomMenu(null);
4347
- }
4386
+ },
4387
+ eventCount: viewEventCount,
4388
+ hideLanguageSwitcher
4348
4389
  }
4349
4390
  ),
4350
4391
  /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ React15__namespace.default.createElement(