@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 +47 -21
- package/dist/index.cjs +544 -642
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -77
- package/dist/index.d.ts +76 -77
- package/dist/index.js +378 -634
- package/dist/index.js.map +1 -1
- package/dist/motion.cjs +544 -642
- package/dist/motion.cjs.map +1 -1
- package/dist/motion.d.cts +4 -3
- package/dist/motion.d.ts +4 -3
- package/dist/motion.js +378 -634
- package/dist/motion.js.map +1 -1
- package/dist/slotline.css +18 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# @nickkupriyanov/slotline-react
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@nickkupriyanov/slotline-react)
|
|
4
|
+
[](https://github.com/nickkupriyanov/slotline/actions/workflows/ci.yml)
|
|
5
|
+
[](https://nickkupriyanov.github.io/slotline/)
|
|
6
|
+
[](https://nickkupriyanov.github.io/slotline/storybook/)
|
|
7
|
+
[](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**: ~
|
|
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
|
|
30
|
-
|
|
|
31
|
-
| `@nickkupriyanov/slotline-react`
|
|
32
|
-
| `@nickkupriyanov/slotline-react/motion`
|
|
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
|
|
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(
|
|
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}`, {
|
|
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(
|
|
54
|
-
const res = await fetch('/api/bookings', {
|
|
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,
|
|
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,
|
|
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 {
|
|
103
|
+
const { snapshot, actions } = useSlotlineContext();
|
|
104
|
+
const view = snapshot.view;
|
|
85
105
|
return (
|
|
86
106
|
<>
|
|
87
|
-
<h2>{
|
|
88
|
-
{
|
|
89
|
-
|
|
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"
|
|
120
|
+
<SlotlineProvider adapter={adapter} theme="dark">
|
|
121
|
+
<Panel />
|
|
122
|
+
</SlotlineProvider>
|
|
97
123
|
);
|
|
98
124
|
```
|
|
99
125
|
|