@nickkupriyanov/slotline-react 0.1.0 → 0.2.0

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
@@ -1,7 +1,15 @@
1
1
  # @nickkupriyanov/slotline-react
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@nickkupriyanov/slotline-react?label=npm)](https://www.npmjs.com/package/@nickkupriyanov/slotline-react)
4
+ [![CI](https://github.com/nickkupriyanov/slotline/actions/workflows/ci.yml/badge.svg)](https://github.com/nickkupriyanov/slotline/actions/workflows/ci.yml)
5
+ [![Live demo](https://img.shields.io/badge/demo-live-ff215a)](https://nickkupriyanov.github.io/slotline/)
6
+ [![Storybook](https://img.shields.io/badge/storybook-open-ff4785)](https://nickkupriyanov.github.io/slotline/storybook/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/nickkupriyanov/slotline/blob/main/LICENSE)
8
+
3
9
  A booking widget for React: **month calendar → time slots → details → confirmation** in one drop-in component.
4
10
 
11
+ **[Live demo](https://nickkupriyanov.github.io/slotline/)** · **[Storybook](https://nickkupriyanov.github.io/slotline/storybook/)**
12
+
5
13
  Dark theme by default, plain-CSS theming, soft animations, `Intl`-correct time zones, and one small adapter that connects it to your backend.
6
14
 
7
15
  ```bash
@@ -22,17 +30,17 @@ export const Booking = () => <Slotline adapter={createMockAdapter()} theme="dark
22
30
  - **Soft animations with zero runtime**: direction-aware step transitions, animated height, staggered slot reveals, a self-drawing check mark. Optional `@nickkupriyanov/slotline-react/motion` entry for spring transitions.
23
31
  - **Accessible**: ARIA grid with roving tabindex, keyboard navigation, live-region step announcements, focus management, labelled controls, `prefers-reduced-motion` support.
24
32
  - **Correct dates**: day keys are time-zone aware, slots are instants, everything formats through `Intl`. `en` and `ru` copy included, any locale tag works.
25
- - **Small**: ~14.5 kB gzip ESM + ~3.6 kB gzip CSS, no runtime dependencies.
33
+ - **Small**: ~29 kB gzip ESM (this package plus the core it depends on) + ~4.5 kB gzip CSS, no third-party runtime dependencies.
26
34
 
27
35
  ## Entries
28
36
 
29
- | Import | What you get |
30
- | --- | --- |
31
- | `@nickkupriyanov/slotline-react` | `Slotline`, `SlotlineProvider`, `SlotlineView`, `useBookingFlow`, `useSlotlineContext`, `createMockAdapter`, types, date and i18n helpers. |
32
- | `@nickkupriyanov/slotline-react/motion` | The same API with a spring-based animation kit. Requires the optional `motion` peer dependency. |
33
- | `@nickkupriyanov/slotline-react/styles.css` | The stylesheet. Import it once. |
37
+ | Import | What you get |
38
+ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
39
+ | `@nickkupriyanov/slotline-react` | `Slotline`, `SlotlineProvider`, `SlotlineView`, `useBookingFlow`, `useSlotlineContext`, `createMockAdapter`, types, date and i18n helpers. |
40
+ | `@nickkupriyanov/slotline-react/motion` | The same API with a spring-based animation kit. Requires the optional `motion` peer dependency. |
41
+ | `@nickkupriyanov/slotline-react/styles.css` | The stylesheet. Import it once. |
34
42
 
35
- The state machine, the `Intl` date helpers, the copy, the class names and the stylesheet come from **`@nickkupriyanov/slotline-core`**, a dependency of this package. React users never import it directly — it exists so another binding can render the same widget against the same core.
43
+ The controller, the derived view model, the reducer, the `Intl` date helpers, the copy, the class names and the stylesheet come from **`@nickkupriyanov/slotline-core`**, a dependency of this package. React users rarely import it directly — it exists so another binding renders the same flow against the same core.
36
44
 
37
45
  ## Adapter
38
46
 
@@ -40,25 +48,36 @@ The state machine, the `Intl` date helpers, the copy, the class names and the st
40
48
  import type { BookingAdapter } from '@nickkupriyanov/slotline-react';
41
49
 
42
50
  export const adapter: BookingAdapter = {
43
- async getAvailability({ from, to, duration, timeZone, signal }) {
44
- const res = await fetch(`/api/availability?from=${from}&to=${to}&duration=${duration}&tz=${timeZone}`, { signal });
51
+ async getAvailability({ from, to, duration, timeZone, context, signal }) {
52
+ const res = await fetch(
53
+ `/api/availability?from=${from}&to=${to}&duration=${duration}&tz=${timeZone}&schedule=${context.key}`,
54
+ { signal },
55
+ );
45
56
  if (!res.ok) throw new Error('Could not load availability');
46
57
  return res.json(); // [{ date: '2026-05-04', slotCount: 8 }, …]
47
58
  },
48
59
  async getSlots({ date, duration, timeZone, signal }) {
49
- const res = await fetch(`/api/slots?date=${date}&duration=${duration}&tz=${timeZone}`, { signal });
60
+ const res = await fetch(`/api/slots?date=${date}&duration=${duration}&tz=${timeZone}`, {
61
+ signal,
62
+ });
50
63
  if (!res.ok) throw new Error('Could not load times');
51
- return res.json(); // [{ start: '2026-05-04T07:15:00Z' }, …]
64
+ return res.json(); // [{ id: '2026-05-04#0915', start: '2026-05-04T07:15:00Z' }, …]
52
65
  },
53
- async createBooking(request) {
54
- const res = await fetch('/api/bookings', { method: 'POST', body: JSON.stringify(request) });
66
+ async createBooking({ slot, values, idempotencyKey }) {
67
+ const res = await fetch('/api/bookings', {
68
+ method: 'POST',
69
+ headers: { 'idempotency-key': idempotencyKey },
70
+ body: JSON.stringify({ slotId: slot.id, values }),
71
+ });
55
72
  if (!res.ok) throw new Error('Booking failed');
56
- return res.json(); // { id, inviteeEmail? }
73
+ return res.json(); // { id, status: 'confirmed', invitation? }
57
74
  },
75
+ // Optional: a repeat of an unknown outcome stays safe for this long.
76
+ capabilities: { idempotentCreate: true, idempotencyRetentionMs: 86_400_000 },
58
77
  };
59
78
  ```
60
79
 
61
- `request` is `{ slot, date, duration, timeZone, details }`. Every query receives an `AbortSignal` tied to the widget's lifetime.
80
+ `request` is `{ slot, date, duration, timeZone, context, values, idempotencyKey }`: every offer has a stable `id`, and `values` is the typed form payload. Every query receives an `AbortSignal` tied to the widget's lifetime.
62
81
 
63
82
  ## Theming
64
83
 
@@ -81,19 +100,26 @@ Set `theme="light" | "dark" | "system"` on the component; the resolved theme lan
81
100
  import { SlotlineProvider, useSlotlineContext } from '@nickkupriyanov/slotline-react';
82
101
 
83
102
  const Panel = () => {
84
- const { state, monthLabel, actions, isDayBookable, monthGrid } = useSlotlineContext();
103
+ const { snapshot, actions } = useSlotlineContext();
104
+ const view = snapshot.view;
85
105
  return (
86
106
  <>
87
- <h2>{monthLabel}</h2>
88
- {monthGrid.filter(isDayBookable).map((day) => (
89
- <button key={day} onClick={() => actions.selectDate(day)}>{day}</button>
90
- ))}
107
+ <h2>{view.month.label}</h2>
108
+ {view.month.grid
109
+ .filter((day) => view.days[day]?.bookable)
110
+ .map((day) => (
111
+ <button key={day} onClick={() => actions.selectDate(day)}>
112
+ {day}
113
+ </button>
114
+ ))}
91
115
  </>
92
116
  );
93
117
  };
94
118
 
95
119
  export const Custom = () => (
96
- <SlotlineProvider adapter={adapter} theme="dark"><Panel /></SlotlineProvider>
120
+ <SlotlineProvider adapter={adapter} theme="dark">
121
+ <Panel />
122
+ </SlotlineProvider>
97
123
  );
98
124
  ```
99
125