@nickkupriyanov/slotline-react 0.1.1 → 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
@@ -30,17 +30,17 @@ export const Booking = () => <Slotline adapter={createMockAdapter()} theme="dark
30
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.
31
31
  - **Accessible**: ARIA grid with roving tabindex, keyboard navigation, live-region step announcements, focus management, labelled controls, `prefers-reduced-motion` support.
32
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.
33
- - **Small**: ~15 kB gzip ESM (this package plus the core it depends on) + ~4.5 kB gzip CSS, no third-party 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.
34
34
 
35
35
  ## Entries
36
36
 
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. |
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. |
42
42
 
43
- 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.
44
44
 
45
45
  ## Adapter
46
46
 
@@ -48,25 +48,36 @@ The state machine, the `Intl` date helpers, the copy, the class names and the st
48
48
  import type { BookingAdapter } from '@nickkupriyanov/slotline-react';
49
49
 
50
50
  export const adapter: BookingAdapter = {
51
- async getAvailability({ from, to, duration, timeZone, signal }) {
52
- 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
+ );
53
56
  if (!res.ok) throw new Error('Could not load availability');
54
57
  return res.json(); // [{ date: '2026-05-04', slotCount: 8 }, …]
55
58
  },
56
59
  async getSlots({ date, duration, timeZone, signal }) {
57
- 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
+ });
58
63
  if (!res.ok) throw new Error('Could not load times');
59
- return res.json(); // [{ start: '2026-05-04T07:15:00Z' }, …]
64
+ return res.json(); // [{ id: '2026-05-04#0915', start: '2026-05-04T07:15:00Z' }, …]
60
65
  },
61
- async createBooking(request) {
62
- 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
+ });
63
72
  if (!res.ok) throw new Error('Booking failed');
64
- return res.json(); // { id, inviteeEmail? }
73
+ return res.json(); // { id, status: 'confirmed', invitation? }
65
74
  },
75
+ // Optional: a repeat of an unknown outcome stays safe for this long.
76
+ capabilities: { idempotentCreate: true, idempotencyRetentionMs: 86_400_000 },
66
77
  };
67
78
  ```
68
79
 
69
- `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.
70
81
 
71
82
  ## Theming
72
83
 
@@ -89,19 +100,26 @@ Set `theme="light" | "dark" | "system"` on the component; the resolved theme lan
89
100
  import { SlotlineProvider, useSlotlineContext } from '@nickkupriyanov/slotline-react';
90
101
 
91
102
  const Panel = () => {
92
- const { state, monthLabel, actions, isDayBookable, monthGrid } = useSlotlineContext();
103
+ const { snapshot, actions } = useSlotlineContext();
104
+ const view = snapshot.view;
93
105
  return (
94
106
  <>
95
- <h2>{monthLabel}</h2>
96
- {monthGrid.filter(isDayBookable).map((day) => (
97
- <button key={day} onClick={() => actions.selectDate(day)}>{day}</button>
98
- ))}
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
+ ))}
99
115
  </>
100
116
  );
101
117
  };
102
118
 
103
119
  export const Custom = () => (
104
- <SlotlineProvider adapter={adapter} theme="dark"><Panel /></SlotlineProvider>
120
+ <SlotlineProvider adapter={adapter} theme="dark">
121
+ <Panel />
122
+ </SlotlineProvider>
105
123
  );
106
124
  ```
107
125