react-native-roster 0.4.1 → 0.4.3
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 +110 -437
- package/dist/src/components/roster/selection/selection-layout.js +1 -1
- package/dist/src/components/roster/selection/selection-layout.js.map +1 -1
- package/dist/src/components/roster/use-roster.d.ts.map +1 -1
- package/dist/src/components/roster/use-roster.js +4 -2
- package/dist/src/components/roster/use-roster.js.map +1 -1
- package/llms.txt +46 -31
- package/package.json +13 -3
- package/src/adapters/rrule/README.md +4 -3
- package/src/components/layers/README.md +1 -1
- package/src/components/roster/README.md +2 -2
- package/src/components/roster/selection/selection-layout.tsx +1 -1
- package/src/components/roster/use-roster.ts +4 -2
- package/src/components/schedule/README.md +2 -2
- package/src/core/README.md +43 -0
- package/src/nativewind/README.md +45 -1
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# react-native-roster
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
[](https://simiancraft.github.io/react-native-roster/)
|
|
3
|
+
**Resource timeline and schedule components for React Native and web, with layered intervals, coverage, provenance, and explicit daylight-saving handling.**
|
|
6
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/react-native-roster)
|
|
7
6
|
[](https://github.com/simiancraft/react-native-roster/actions/workflows/ci.yml)
|
|
8
7
|
[](https://codecov.io/github/simiancraft/react-native-roster)
|
|
9
8
|
[](#contract)
|
|
@@ -12,32 +11,25 @@
|
|
|
12
11
|
[](./LICENSE)
|
|
13
12
|
[](https://securityscorecards.dev/viewer/?uri=github.com/simiancraft/react-native-roster)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
z-order; coverage math unions availability, subtracts bookings, and sorts.
|
|
35
|
-
- **Recurrence adapter.** Weekly, daily, and monthly rules with exclusions,
|
|
36
|
-
caps, and content-keyed caches, kept out of the core.
|
|
37
|
-
- **Zones everywhere.** Input-bearing regions accept component types, singletons accept nodes, and
|
|
38
|
-
every chrome region takes `style` plus a NativeWind `className` twin.
|
|
39
|
-
- **Virtualized and measured.** Geometry runs only for mounted lanes; CI gates
|
|
40
|
-
layout, coverage, bundle size, and browser action budgets.
|
|
14
|
+
**Live demo: [explore the gallery](https://simiancraft.github.io/react-native-roster/)
|
|
15
|
+
or [try the showcase](https://simiancraft.github.io/react-native-roster/showcase).**
|
|
16
|
+
|
|
17
|
+
<!-- HERO GIF PLACEHOLDER: insert a recorded gallery or showcase tour here when available. -->
|
|
18
|
+
|
|
19
|
+
Give it lanes of absolute intervals and their sources. Get a resource timeline
|
|
20
|
+
across many lanes, or a day-column schedule for one lane, with coverage totals
|
|
21
|
+
and press callbacks that explain exactly which sources produced each span.
|
|
22
|
+
|
|
23
|
+
**It renders who is on, when, and why.** Press any covered or removed span and
|
|
24
|
+
get back the rules, rows, or exclusions behind it; removed time keeps its
|
|
25
|
+
exclusion sources. Daylight-saving transitions are drawn honestly: skipped hours
|
|
26
|
+
are hatched, repeated hours have separate geometry, and a week containing a
|
|
27
|
+
transition is 167 or 169 hours wide. `Roster` and `Schedule` project the same
|
|
28
|
+
lane data; `useRoster` and `useSchedule` expose their models for custom layouts.
|
|
29
|
+
|
|
30
|
+
Consumers own creation, dragging, resizing, and persistence. It ships
|
|
31
|
+
virtualized roster lanes, replaceable components, NativeWind chrome, and a small
|
|
32
|
+
standard JavaScript and `Intl` core with a [15 kB size gate](./.size-limit.json).
|
|
41
33
|
|
|
42
34
|
## Install
|
|
43
35
|
|
|
@@ -46,19 +38,29 @@ bun add react-native-roster @legendapp/list
|
|
|
46
38
|
bunx expo install react-native-reanimated --bun
|
|
47
39
|
```
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
For web, also install React DOM and React Native Web versions compatible with
|
|
42
|
+
your React/Expo setup, plus `bun add @radix-ui/react-popover@^1.1.23`.
|
|
43
|
+
**Radix is required whenever a web bundler resolves the root entry or
|
|
44
|
+
`/nativewind` to the web selection layout, even with selection disabled or only
|
|
45
|
+
`Schedule` imported.** That layout imports Radix unconditionally; the published
|
|
46
|
+
browser remap and Metro's web source resolution both reach it. Native and
|
|
47
|
+
`/core`-only or `/rrule`-only imports do not require Radix.
|
|
48
|
+
|
|
49
|
+
Peer ranges: React >=18.2, React Native >=0.74, LegendList >=2, Reanimated >=3.19,
|
|
50
|
+
Expo >=51 (optional), NativeWind >=4.1 (optional), and Radix ^1.1.23 (web).
|
|
51
|
+
Reanimated is required by `Roster` and optional for core-only use. Keep React,
|
|
52
|
+
React Native, and Expo aligned with your SDK. The demo uses Expo SDK 54,
|
|
53
|
+
React 19.1, React Native 0.81.5, LegendList 2.0.19, and Reanimated 3.19.5;
|
|
54
|
+
these versions do not establish support for every peer-range combination.
|
|
55
|
+
|
|
56
|
+
On Metro before React Native 0.79, enable `resolver.unstable_enablePackageExports`
|
|
57
|
+
so the subpaths resolve. Keep one React copy in workspaces.
|
|
58
|
+
`rrule-temporal` and `@js-temporal/polyfill` install as ordinary dependencies;
|
|
59
|
+
only `/rrule` imports them. Import isolation does not reduce installation size.
|
|
58
60
|
|
|
59
61
|
## Quick start: static intervals
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
Give the component a bounded height. No adapter is needed for absolute intervals.
|
|
62
64
|
|
|
63
65
|
```tsx
|
|
64
66
|
import { Roster } from 'react-native-roster';
|
|
@@ -86,55 +88,17 @@ export function Example() {
|
|
|
86
88
|
Press the interval to receive its `table / row-one` source. `minuteStep`
|
|
87
89
|
defaults to 60 and accepts any positive divisor of 60; it changes ticks and
|
|
88
90
|
empty-space snapping, never interval boundaries. `rowHeight` defaults to 48 and
|
|
89
|
-
`pxPerMinute` to
|
|
91
|
+
`pxPerMinute` to 0.5, growing to fit wider viewports. Both scale and row height
|
|
92
|
+
must be positive and finite.
|
|
90
93
|
|
|
91
94
|
## Quick start: recurring local hours
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const spec: WindowSpec = {
|
|
101
|
-
span: 'week', anchorDate: '2024-01-01', timezone: 'America/Chicago',
|
|
102
|
-
};
|
|
103
|
-
const set: RuleSet = {
|
|
104
|
-
rules: [{
|
|
105
|
-
id: 'weekday-hours', kind: 'include', frequency: 'WEEKLY',
|
|
106
|
-
dtstart: '2024-01-01', byweekday: [0, 1, 2, 3, 4],
|
|
107
|
-
hourstart: 9, hourend: 17, timezone: 'America/Chicago',
|
|
108
|
-
}],
|
|
109
|
-
dates: [{
|
|
110
|
-
id: 'closed', kind: 'exclude', date: '2024-01-02',
|
|
111
|
-
timezone: 'America/Chicago', note: 'Closed all day',
|
|
112
|
-
}],
|
|
113
|
-
};
|
|
114
|
-
const result = expandRuleSet(set, windowFor(spec));
|
|
115
|
-
const lane: Lane = {
|
|
116
|
-
id: 'one', label: 'Lane one', timezone: 'America/Chicago',
|
|
117
|
-
complete: result.complete,
|
|
118
|
-
layers: [{
|
|
119
|
-
id: 'open', role: 'availability', z: 0, style: { color: '#4f9478' },
|
|
120
|
-
intervals: result.intervals, gaps: result.gaps,
|
|
121
|
-
}],
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export function RecurringExample() {
|
|
125
|
-
return <Roster lanes={[lane]} windowSpec={spec}
|
|
126
|
-
style={{ height: 480, flex: undefined }}
|
|
127
|
-
onGapPress={(rect) => console.log(rect.sources)} />;
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Tuesday is a gap carrying the dated exclusion; the other weekdays carry the
|
|
132
|
-
include rule. Expand once per `windowFor(spec)` in your screen's hook, never
|
|
133
|
-
inside a slot component; content-keyed caches reuse unchanged occurrences. The
|
|
134
|
-
[adapter guide](./docs/adapters.md) covers validation, caps, cache lifetime,
|
|
135
|
-
and mapping tables or feeds without a recurrence dependency;
|
|
136
|
-
[recurrence semantics](./docs/recurrence.md) records the exact anchor, UNTIL,
|
|
137
|
-
COUNT, and BYSETPOS rules.
|
|
96
|
+
Import `expandRuleSet` from `/rrule` to turn daily, weekly, or monthly local
|
|
97
|
+
hours and dated exclusions into intervals and gaps. Expand for `windowFor(spec)`
|
|
98
|
+
in your screen's hook, and carry `result.complete` into `Lane.complete`.
|
|
99
|
+
The [runnable recurring example](./docs/adapters.md#quick-start-recurring-local-hours)
|
|
100
|
+
shows the complete mapping; [recurrence semantics](./docs/recurrence.md) covers
|
|
101
|
+
validation, COUNT, UNTIL, and BYSETPOS. Never expand inside a slot component.
|
|
138
102
|
|
|
139
103
|
## Contract
|
|
140
104
|
|
|
@@ -172,58 +136,26 @@ and "Availability may be incomplete"; both are localizable props.
|
|
|
172
136
|
| `react-native-roster/rrule` | `expandRuleSet`, `envelopeFor`, types, and expansion counters and caches. Uses pinned `rrule-temporal` and `@js-temporal/polyfill`. |
|
|
173
137
|
| `react-native-roster/nativewind` | Registers `Roster` and `Schedule` with NativeWind so their `className` props resolve; re-exports the registered components. |
|
|
174
138
|
|
|
175
|
-
Root and core never import recurrence dependencies.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
139
|
+
Root and core never import recurrence dependencies. Metro selects TypeScript
|
|
140
|
+
source; the default condition selects CommonJS, and `types` selects declarations
|
|
141
|
+
in `dist/src`.
|
|
142
|
+
The CommonJS root pulls in the root bundle; use `/core` for core-only consumers.
|
|
143
|
+
|
|
144
|
+
## Caches
|
|
145
|
+
|
|
146
|
+
Bump a supplied `lane.version` whenever layers change; otherwise structural layer
|
|
147
|
+
content supplies the key. Treat returned references as read-only. Consumers own
|
|
148
|
+
cache lifetime: clear layout, coverage, and expansion caches when discarding old
|
|
149
|
+
windows. Coverage runs for all lanes; geometry runs for mounted lanes.
|
|
150
|
+
[Cache keys, counters, and clearing](./docs/caches.md) explain target-warm reuse.
|
|
182
151
|
|
|
183
152
|
## Core
|
|
184
153
|
|
|
185
154
|
### Interval helpers
|
|
186
155
|
|
|
187
|
-
`
|
|
188
|
-
windows
|
|
189
|
-
|
|
190
|
-
and its windows remain untouched. Like `extentOf` and `intersectionOf`, it uses
|
|
191
|
-
end-exclusive epoch milliseconds and throws `RangeError` for non-finite bounds
|
|
192
|
-
or `end <= start`.
|
|
193
|
-
|
|
194
|
-
Collapse each person's segments with `extentOf`, then use `intersectionOf` to
|
|
195
|
-
find the shared time across those windows. Each extent bridges gaps between
|
|
196
|
-
segments, so the result describes the collapsed windows.
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
import type { Window } from 'react-native-roster/core';
|
|
200
|
-
import { extentOf, intersectionOf } from 'react-native-roster/core';
|
|
201
|
-
|
|
202
|
-
const people: { name: string; segments: Window[] }[] = [
|
|
203
|
-
{
|
|
204
|
-
name: 'Alex',
|
|
205
|
-
segments: [
|
|
206
|
-
{ start: 1_000, end: 3_000 },
|
|
207
|
-
{ start: 4_000, end: 8_000 },
|
|
208
|
-
],
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: 'Sam',
|
|
212
|
-
segments: [
|
|
213
|
-
{ start: 2_000, end: 5_000 },
|
|
214
|
-
{ start: 6_000, end: 9_000 },
|
|
215
|
-
],
|
|
216
|
-
},
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
const windows = people.map((person): Window => {
|
|
220
|
-
const extent = extentOf(person.segments);
|
|
221
|
-
if (extent === null) throw new Error(`${person.name} has no segments`);
|
|
222
|
-
return extent;
|
|
223
|
-
});
|
|
224
|
-
const sharedTime = intersectionOf(windows); // { start: 2_000, end: 8_000 }
|
|
225
|
-
// A null result from intersectionOf means there is no shared time.
|
|
226
|
-
```
|
|
156
|
+
`extentOf`, `intersectionOf`, and `unionOf` operate on end-exclusive absolute
|
|
157
|
+
windows. Extents bridge gaps; unions preserve disjoint spans. See the
|
|
158
|
+
[helper walkthrough](./src/core/README.md#interval-helper-walkthrough).
|
|
227
159
|
|
|
228
160
|
## Navigation and interaction
|
|
229
161
|
|
|
@@ -249,327 +181,54 @@ to `byLabel`; `byCoverage({ measure })` sorts by coverage descending.
|
|
|
249
181
|
|
|
250
182
|
## Now line
|
|
251
183
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
The caller owns clock updates; Roster starts no timer. `useRoster` exposes `now` and `nowLine`.
|
|
256
|
-
`now` retains the raw instant. `nowLine` (`{ x, now }`) uses the fitted
|
|
257
|
-
`pxPerMinute` scale and is null when `now` is null or outside the window.
|
|
258
|
-
Replace `nowLineComponent` to customize the line using `RosterNowLineInput`
|
|
259
|
-
(`{ x, now }`). The line follows horizontal scrolling; changing `now` does not
|
|
260
|
-
invalidate mounted lanes. The every-zone gallery fixture can toggle a fixed
|
|
261
|
-
instant at the window midpoint.
|
|
184
|
+
Pass controlled `now={timestamp}` to Roster; `null` (the default) draws nothing.
|
|
185
|
+
The caller owns clock updates. Only instants inside the end-exclusive window are visible. Replace `nowLineComponent` to style it; the
|
|
186
|
+
[Roster reference](./src/components/roster/README.md) covers hook and scroll behavior.
|
|
262
187
|
|
|
263
188
|
## Roster zones
|
|
264
189
|
|
|
265
|
-
Props ending in `Component` accept
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
or press behavior. Pass `null` to a node slot to suppress its default.
|
|
270
|
-
|
|
271
|
-
| Slot | Component inputs or node | Default and behavior |
|
|
272
|
-
| --- | --- | --- |
|
|
273
|
-
| `emptyZone` | `ReactNode` | `RosterEmpty`: No lanes. |
|
|
274
|
-
| `cornerZone` | `ReactNode` | `RosterCorner`: nothing; the cell above the labels, `laneLabelWidth` wide. |
|
|
275
|
-
| `laneLabelComponent` | `lane`, `flag`, `complete`, `viewTimezone`, localized labels | `RosterLaneLabel`: label, differing IANA zone badge, and notices. |
|
|
276
|
-
| `headerCellComponent` | `HeaderCellInput` (`tick`) | `RosterHeaderCell`: tick label. |
|
|
277
|
-
| `intervalComponent` | `rect`, `layer`, `lane`, `highlighted` | `RosterInterval`: positioned colored rect, with final inset bounds. |
|
|
278
|
-
| `intervalDetailComponent` | `rect`, `layer`, `lane`, `highlighted`, absolute `start` and `end`, `viewTimezone` | Absent by default; enables selection and fills its details. |
|
|
279
|
-
| `selectionLayout` | `SelectionLayoutProps`: nodes, targetBounds, open, dismissal, host, and shared scroll | `RosterSelectionPopover`: native portal or Radix web popover; replace at runtime. |
|
|
280
|
-
| `gapComponent` | `rect`, `layer`, `lane` | `RosterGap`: no visible content; the row supplies pressable bounds. |
|
|
281
|
-
| `nowLineComponent` | `RosterNowLineInput` (`x`, `now`) | `RosterNowLine`: noninteractive vertical red line across the body. |
|
|
282
|
-
| `gridComponent` | `ticks`, `contentWidth` | `RosterGrid`: one hairline per tick behind every lane. |
|
|
283
|
-
| `headerComponent` | `ticks`, `projection`, `scroll`, `contentWidth`, `headerCellComponent` | `RosterHeader`: frozen header following horizontal offset. |
|
|
284
|
-
| `laneLabelColumnComponent` | `labels`, `projection`, `scroll`, `laneLabelComponent` | `RosterLaneLabelColumn`: frozen labels following vertical offset. |
|
|
285
|
-
| `bodyComponent` | Ordered `lanes`, `window`, `geometryFor`, `projection`, `scroll`, `press`, `ticks`, `viewport`, `contentWidth`, `nowLine`, `nowLineComponent`, highlight and hover, incomplete label, and rect components | `RosterBody`: virtualized lanes. |
|
|
286
|
-
|
|
287
|
-
`RosterBody` composes `RosterBodyLayout`, which arranges `gridZone`, `listZone`, and optional `overlayZone`
|
|
288
|
-
nodes with scroll wiring, and `RosterLaneList`, which owns LegendList and its
|
|
289
|
-
per-lane callback. The body waits for viewport measurement before mounting the list.
|
|
290
|
-
|
|
291
|
-
```tsx
|
|
292
|
-
import type { LaneLabelInput } from 'react-native-roster';
|
|
293
|
-
import { Text } from 'react-native';
|
|
294
|
-
|
|
295
|
-
function LaneLabel({ lane }: LaneLabelInput) {
|
|
296
|
-
return <Text>{lane.label}</Text>;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
<Roster lanes={lanes} windowSpec={windowSpec}
|
|
300
|
-
laneLabelComponent={LaneLabel} cornerZone={<Text>People</Text>} />
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
A custom interval component positions at `rect.x/y`, uses `rect.width/height/z`,
|
|
304
|
-
and sets `pointerEvents="none"` so the parent hit-test walk owns presses. Gap
|
|
305
|
-
fillers are already inside positioned pressables. `useRoster` exposes ordered
|
|
306
|
-
lanes, coverage, lane state, ticks, `geometryFor`, shared scrolling, `press`,
|
|
307
|
-
viewport measurement, navigation, and status for fully custom layouts.
|
|
308
|
-
|
|
309
|
-
Chrome regions take style props: `style`, `headerStyle` (the 40 px header row
|
|
310
|
-
holding the corner and ticks), `laneLabelColumnStyle`, and `bodyStyle`.
|
|
311
|
-
`laneLabelWidth` sizes the corner and label column, default 180. Each style
|
|
312
|
-
prop has a `className` twin; see [NativeWind](#nativewind).
|
|
190
|
+
Props ending in `Component` accept component types; props ending in `Zone`
|
|
191
|
+
accept React nodes. Define slot components at module scope to preserve state.
|
|
192
|
+
Use exported defaults inside replacements to retain geometry and interaction.
|
|
193
|
+
The [slot tables and examples](./docs/customization.md#roster-zones) cover every region.
|
|
313
194
|
|
|
314
195
|
### Selection
|
|
315
196
|
|
|
316
|
-
Add `intervalDetailComponent`
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
and web; web outside press and Escape still dismiss.
|
|
323
|
-
Removing the selected lane, layer, or interval bounds clears selection. The private
|
|
324
|
-
reconcileSelection helper matches source identity sets and chooses nearest bounds with
|
|
325
|
-
a total difference of at most 1 ms for projection roundoff.
|
|
326
|
-
|
|
327
|
-
Native has no intercepting dismissal overlay; body presses reach the hook, and the detail
|
|
328
|
-
card captures its own presses. Web excludes the body wrapper from Radix outside
|
|
329
|
-
dismissal, leaving body presses to the hook while preserving true outside presses and Escape.
|
|
330
|
-
The hook toggles the selected interval closed, switches to another interval, and dismisses
|
|
331
|
-
on cell or gap presses while preserving their callbacks. The body restores horizontal
|
|
332
|
-
and vertical offsets from shared values on remount when selectionLayout changes.
|
|
333
|
-
A mount effect calls the horizontal ScrollView ref's scrollTo without animation;
|
|
334
|
-
native also retains contentOffset. LegendList restores initialScrollOffset through
|
|
335
|
-
its own web mount effect and native initial offset.
|
|
336
|
-
|
|
337
|
-
Current data and geometry replace old references, including after resizing or sorting. Reconciliation matches the layer id and
|
|
338
|
-
order-insensitive source identities, then chooses the nearest absolute bounds. The sum
|
|
339
|
-
of bound differences must be at most 1 ms. Stored bounds remain the display values.
|
|
340
|
-
|
|
341
|
-
`selectable?: boolean` belongs only to the hook input, `RosterInput`, and defaults to false.
|
|
342
|
-
It is excluded from `RosterProps`. Hook consumers
|
|
343
|
-
pass `selectable: true` to `useRoster` to retain selection. `intervalDetailComponent`,
|
|
344
|
-
`selectionLayout`, and `portalHost` live on `RosterProps` because the chassis mounts
|
|
345
|
-
the content and layout. The chassis enables selection with
|
|
346
|
-
`selectable: Boolean(intervalDetailComponent)`. Custom chassis mount their layout
|
|
347
|
-
with the returned selection, dismissal, and scroll inputs.
|
|
348
|
-
|
|
349
|
-
```tsx
|
|
350
|
-
import { Text, View } from 'react-native';
|
|
351
|
-
import { Roster } from 'react-native-roster';
|
|
352
|
-
import type { IntervalDetailInput, Lane } from 'react-native-roster';
|
|
353
|
-
|
|
354
|
-
function IntervalDetails({ lane, layer, rect, start, end, viewTimezone }: IntervalDetailInput) {
|
|
355
|
-
const format = new Intl.DateTimeFormat('en-US', { timeZone: viewTimezone, timeStyle: 'short' });
|
|
356
|
-
return <View style={{ padding: 16, backgroundColor: 'white' }}>
|
|
357
|
-
<Text>{lane.label}: {layer.label ?? layer.id}</Text>
|
|
358
|
-
<Text>{format.format(start)} to {format.format(end)}</Text>
|
|
359
|
-
{rect.sources.map((source) =>
|
|
360
|
-
<Text key={JSON.stringify([source.kind, source.id])}>
|
|
361
|
-
{source.label ?? source.id}
|
|
362
|
-
</Text>)}
|
|
363
|
-
</View>;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export function SelectionExample({ lanes }: { lanes: Lane[] }) {
|
|
367
|
-
return <Roster lanes={lanes}
|
|
368
|
-
windowSpec={{ span: 'day', anchorDate: '2024-01-01', timezone: 'UTC' }}
|
|
369
|
-
style={{ height: 480, flex: undefined }}
|
|
370
|
-
intervalDetailComponent={IntervalDetails} />;
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
Web consumers install `bun add @radix-ui/react-popover`. It is an optional peer
|
|
375
|
-
for native and core-only consumers, and required by the web root entry. Consumers
|
|
376
|
-
resolving the library from source with a web bundler (Vite, Storybook, or Metro web)
|
|
377
|
-
must install it even without enabling selection because `selection-layout.web.tsx`
|
|
378
|
-
is in the module graph. Native and core-only consumers do not need it.
|
|
379
|
-
`RosterSelectionPopover` selects native or web through platform resolution.
|
|
380
|
-
Native mounts a local `PortalHost` and registers `Portal` content; web uses Radix
|
|
381
|
-
for portal placement, outside press, Escape, and collisions. The layout returns focus
|
|
382
|
-
to the previously focused element only after Escape.
|
|
383
|
-
The overlay tracks both scroll offsets with Reanimated shared values, without
|
|
384
|
-
React scroll state. Native clamps details horizontally to the measured viewport,
|
|
385
|
-
flips above when that fits, and uses the top edge when neither vertical placement
|
|
386
|
-
fits. The default native popover waits for viewport measurement and scrolls oversized
|
|
387
|
-
content on both axes within a maximum size of the viewport minus 8 px on each axis.
|
|
388
|
-
Consumers wanting a different presentation supply `selectionLayout`.
|
|
389
|
-
Native also dismisses on hardware back.
|
|
390
|
-
|
|
391
|
-
`selectionLayout?: ComponentType<SelectionLayoutProps>` is the layout strategy
|
|
392
|
-
naming exception to the `Component` suffix. The chassis mounts it with `anchorZone`
|
|
393
|
-
(the body), `contentZone` (details or null), `targetBounds` (body-content bounds), `open`,
|
|
394
|
-
`onDismiss`, `portalHost`, and `scroll`. The target bounds include the lane offset and
|
|
395
|
-
interval inset. Layout scroll inputs are limited to `x`, `y`, `headerStyle`, and
|
|
396
|
-
`labelStyle`. Render each node once. A consumer inspector
|
|
397
|
-
can arrange the nodes in columns and call `onDismiss` from its close button.
|
|
398
|
-
The [interval-detail gallery route](./demo/app/gallery/interval-detail.tsx)
|
|
399
|
-
switches presentations at runtime.
|
|
400
|
-
|
|
401
|
-
`portalHost` overrides the native destination name, whose default uses a unique
|
|
402
|
-
per-roster `useId`. Give separate rosters separate override names. The default
|
|
403
|
-
layout owns its host; do not also mount that name at an ancestor. Custom layouts
|
|
404
|
-
can instead target their own ancestor host, using the exported `PortalHost` and
|
|
405
|
-
`Portal`. A store-based portal does not preserve context from the registration
|
|
406
|
-
site; place required providers above the host or re-provide them in the content.
|
|
407
|
-
Selection never enters the lane list's body content key.
|
|
408
|
-
|
|
409
|
-
Schedule does not yet support selection; it is a later change.
|
|
197
|
+
Add `intervalDetailComponent` for pressed-interval details. Pressing the same
|
|
198
|
+
interval dismisses it; pressing another switches selection. Cell and gap presses
|
|
199
|
+
also dismiss, preserving their callbacks. Web outside press and Escape dismiss.
|
|
200
|
+
Schedule does not support selection. Custom `selectionLayout` implementations
|
|
201
|
+
must follow the [layout and portal contract](./docs/customization.md#selection),
|
|
202
|
+
including unique native host names and provider placement above the host.
|
|
410
203
|
|
|
411
204
|
### Consumer data in slot components
|
|
412
205
|
|
|
413
|
-
|
|
414
|
-
`Roster` and read it with `useContext` in a module-scope slot component. This
|
|
415
|
-
example supplies density and locale while keeping the header cell type stable:
|
|
416
|
-
|
|
417
|
-
```tsx
|
|
418
|
-
import { createContext, useContext } from 'react';
|
|
419
|
-
import { Text } from 'react-native';
|
|
420
|
-
import type { HeaderCellInput, RosterProps } from 'react-native-roster';
|
|
421
|
-
import { Roster } from 'react-native-roster';
|
|
422
|
-
|
|
423
|
-
type SlotPreferences = {
|
|
424
|
-
density: 'compact' | 'comfortable';
|
|
425
|
-
locale: string;
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
const SlotPreferencesContext = createContext<SlotPreferences>({
|
|
429
|
-
density: 'comfortable',
|
|
430
|
-
locale: 'en-US',
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
function LocalizedHeaderCell({ tick }: HeaderCellInput) {
|
|
434
|
-
const { density, locale } = useContext(SlotPreferencesContext);
|
|
435
|
-
const label = new Intl.DateTimeFormat(locale, {
|
|
436
|
-
timeZone: 'UTC',
|
|
437
|
-
...(tick.kind === 'day'
|
|
438
|
-
? { month: 'short', day: 'numeric' }
|
|
439
|
-
: { hour: 'numeric', minute: '2-digit' }),
|
|
440
|
-
}).format(tick.time);
|
|
441
|
-
|
|
442
|
-
return (
|
|
443
|
-
<Text numberOfLines={1} style={{ padding: density === 'compact' ? 2 : 6 }}>
|
|
444
|
-
{label}
|
|
445
|
-
</Text>
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
export function ConsumerRoster({ lanes }: Pick<RosterProps, 'lanes'>) {
|
|
450
|
-
return (
|
|
451
|
-
<SlotPreferencesContext.Provider value={{ density: 'compact', locale: 'en-GB' }}>
|
|
452
|
-
<Roster
|
|
453
|
-
lanes={lanes}
|
|
454
|
-
windowSpec={{ span: 'week', anchorDate: '2024-01-01', timezone: 'UTC' }}
|
|
455
|
-
style={{ height: 480, flex: undefined }}
|
|
456
|
-
headerCellComponent={LocalizedHeaderCell}
|
|
457
|
-
/>
|
|
458
|
-
</SlotPreferencesContext.Provider>
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
An inline closure or a memoized factory that returns a new component per render
|
|
464
|
-
is not the recommended path. A new interval component type remounts every interval
|
|
465
|
-
and defeats the body's content key. Context supplies changing consumer data while
|
|
466
|
-
preserving the module-scope component type.
|
|
206
|
+
Use context with stable slot types; see the [complete recipe](./docs/customization.md#consumer-data-in-slot-components).
|
|
467
207
|
|
|
468
208
|
## Schedule and its zones
|
|
469
209
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
return <Schedule lane={lane} minuteStep={60}
|
|
476
|
-
style={{ height: 600, flex: undefined }}
|
|
477
|
-
windowSpec={{ span: 'week', anchorDate: '2024-01-01', timezone: 'UTC' }}
|
|
478
|
-
onIntervalPress={(rect) => console.log(rect.sources)} />;
|
|
479
|
-
}
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
Only day and week specs are accepted. The component measures its width,
|
|
483
|
-
reserves a 48 px gutter, and fits the day columns without horizontal scrolling.
|
|
484
|
-
`pxPerHour` defaults to 48. Chrome takes `style`, `headerStyle`, `gutterStyle`,
|
|
485
|
-
and `daysStyle`, each with a `className` twin.
|
|
486
|
-
|
|
487
|
-
| Slot | Component inputs or node | Default and behavior |
|
|
488
|
-
| --- | --- | --- |
|
|
489
|
-
| `gutterComponent` | `hours`, `pxPerHour` | `ScheduleGutter`: 24 frozen hour labels. |
|
|
490
|
-
| `gridComponent` | `hours`, `pxPerHour` | `ScheduleGrid`: 24 bordered hour bands behind each day's rects. |
|
|
491
|
-
| `dayHeaderComponent` | `day` | `ScheduleDayHeader`: weekday, localDate, and transition badge. |
|
|
492
|
-
| `skippedDateComponent` | `localDate` | `ScheduleSkippedDate`: zero-width header marker for a wholly skipped date. |
|
|
493
|
-
| `columnComponent` | `day`, `rects`, `gapRects`, `lane`, `highlightSource`, `press`, interval and gap components | `ScheduleColumn`: final rect bounds in layer order. |
|
|
494
|
-
| `transitionComponent` | `day`, `transition`, `y`, `dividerY`, `height`, `width` | `ScheduleTransition`: skipped-time hatch, or repeat divider and again label. |
|
|
495
|
-
| `nowLineComponent` | `y`, `column` | `ScheduleNowLine`: line in the current day's column, updated each minute. |
|
|
496
|
-
| `intervalComponent`, `gapComponent` | Same inputs as Roster | Shared `RosterInterval` and `RosterGap`. |
|
|
497
|
-
| `incompleteComponent` | `lane`, `label` | `ScheduleIncomplete`: notice above the grid when the lane is incomplete. |
|
|
210
|
+
Pass the same lane to `<Schedule lane={lane} windowSpec={spec} />` with a bounded
|
|
211
|
+
height. Schedule accepts day and week specs, fits columns to measured width,
|
|
212
|
+
and reserves a 48 px gutter. `pxPerHour` defaults to 48 and must be positive and
|
|
213
|
+
finite. Its automatic now line updates each minute. See the
|
|
214
|
+
[example and slot table](./docs/customization.md#schedule-zones).
|
|
498
215
|
|
|
499
216
|
## NativeWind
|
|
500
217
|
|
|
501
|
-
|
|
502
|
-
Every chrome style prop has a
|
|
503
|
-
`
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
Native Web renders object styles inline, and inline paint would otherwise beat
|
|
508
|
-
any class.
|
|
509
|
-
|
|
510
|
-
Set up NativeWind 4 as usual (the demo's [babel.config.js](./demo/babel.config.js),
|
|
511
|
-
[metro.config.js](./demo/metro.config.js), [tailwind.config.js](./demo/tailwind.config.js),
|
|
512
|
-
and [global.css](./demo/global.css) are a working reference), add the optional
|
|
513
|
-
peer with `bun add nativewind`, and register the components once at your app
|
|
514
|
-
root:
|
|
515
|
-
|
|
516
|
-
```tsx
|
|
517
|
-
// app/_layout.tsx
|
|
518
|
-
import '../global.css';
|
|
519
|
-
import 'react-native-roster/nativewind';
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
```tsx
|
|
523
|
-
import { Roster } from 'react-native-roster';
|
|
524
|
-
|
|
525
|
-
<Roster
|
|
526
|
-
lanes={lanes}
|
|
527
|
-
windowSpec={windowSpec}
|
|
528
|
-
className="flex-1 rounded-xl bg-background"
|
|
529
|
-
headerClassName="border-b border-border bg-card"
|
|
530
|
-
laneLabelColumnClassName="border-r border-border bg-card"
|
|
531
|
-
laneLabelWidth={220}
|
|
532
|
-
/>
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
Slot components are ordinary React Native views, so a custom `laneLabelComponent` or
|
|
536
|
-
`intervalComponent` uses `className` on `View` and `Text` directly. Without the
|
|
537
|
-
entry point, class props are ignored and style props still work. The entry
|
|
538
|
-
point is the package's only module with side effects and is listed in
|
|
539
|
-
`sideEffects`. The [showcase route](./demo/app/showcase.tsx) styles every region
|
|
540
|
-
and zone this way in light and dark palettes.
|
|
218
|
+
Install NativeWind >=4.1 and configure it for your app, then import
|
|
219
|
+
`react-native-roster/nativewind` once at the root. Every chrome style prop has a
|
|
220
|
+
`className` twin; classes replace default paint while preserving structure.
|
|
221
|
+
Slot components use `className` on ordinary views. Without registration, class
|
|
222
|
+
props are ignored. [Setup, examples, and demo configuration](./src/nativewind/README.md#setup-and-styling)
|
|
223
|
+
show all regions, including the [showcase route](./demo/app/showcase.tsx).
|
|
541
224
|
|
|
542
225
|
## Gallery and platform support
|
|
543
226
|
|
|
544
|
-
The
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
- Empty, one, 20, and 200 lanes; inset layers; default and replaced zones.
|
|
550
|
-
- Day, week, and month routes at 15, 30, and 60-minute steps.
|
|
551
|
-
- Full-day gaps, lane flags, equal-z precedence, highlight, coverage sorting,
|
|
552
|
-
and incomplete expansion.
|
|
553
|
-
- Chicago DST weeks, mixed rule, lane, and view zones, and editable adapter routes.
|
|
554
|
-
- Schedule empty, layered, excluded, spring, fall, Lord Howe, Apia, incomplete,
|
|
555
|
-
midnight, side-by-side projections, and every zone.
|
|
556
|
-
- The showcase at `/showcase`: twelve generated people across seven zones,
|
|
557
|
-
weekly hours and exclusions from the recurrence adapter, booked events, day
|
|
558
|
-
and week spans, sorting, filtering, and a per-person Schedule, styled with
|
|
559
|
-
NativeWind class props and slot components, with a sun and moon theme toggle.
|
|
560
|
-
Names, titles, and the organization come from `@faker-js/faker` with a fixed
|
|
561
|
-
seed; any resemblance to real people is coincidental.
|
|
562
|
-
Each event compares scheduled time with seeded attendee arrivals and departures.
|
|
563
|
-
A fixed seeded now separates past, live, and future events; attendees are expected,
|
|
564
|
-
pending, present, attended, or absent. Present spans extend through now without
|
|
565
|
-
exposing future departures, including after scheduled end until actual departure.
|
|
566
|
-
A bottom strip draws the union of attendances over the lighter scheduled block,
|
|
567
|
-
preserving gaps and overhang. The event detail popover shares a shaded scheduled
|
|
568
|
-
band across attendance rows. Its footer status line defaults to the attendance
|
|
569
|
-
legend and scheduled-band explanation; web hover or visible keyboard focus shows
|
|
570
|
-
row detail, and native taps toggle detail or switch rows. The inspector retains
|
|
571
|
-
member selection. This feature lives in
|
|
572
|
-
[demo/components/team-roster](./demo/components/team-roster/README.md).
|
|
227
|
+
The gallery covers lane counts, layer precedence, coverage sorting, provenance,
|
|
228
|
+
DST transitions, editable recurrence, and replaceable components. The showcase
|
|
229
|
+
combines filtering, per-lane schedules, and attendance details. See the
|
|
230
|
+
[fixture catalog and local setup](./demo/components/gallery/README.md#run-the-gallery-and-explore-fixtures)
|
|
231
|
+
and [showcase tour](./demo/components/team-roster/README.md#showcase-tour).
|
|
573
232
|
|
|
574
233
|
| Platform | Support and evidence |
|
|
575
234
|
| --- | --- |
|
|
@@ -585,11 +244,25 @@ Workload W is 200 lanes, one week, two layers, a 24-lane viewport, and
|
|
|
585
244
|
[committed CI baseline](./test/performance/baseline.json) on GitHub Actions
|
|
586
245
|
ubuntu-latest records target-cold layout of 24 lanes at 2.070 ms and coverage of
|
|
587
246
|
all 200 lanes at 1.063 ms, gated below 16 ms everywhere and within 1.5 times the
|
|
588
|
-
baseline in CI.
|
|
247
|
+
baseline in CI. Minified, uncompressed size gates with peers external hold core under 15 kB and root under 52 kB. Device
|
|
589
248
|
captures and the paired comparison against other libraries are described, not
|
|
590
249
|
yet recorded, in the [performance guide](./docs/performance.md); CI timings are
|
|
591
250
|
not phone frame rates.
|
|
592
251
|
|
|
252
|
+
## Compared to
|
|
253
|
+
|
|
254
|
+
| Alternative | Choose it for | What react-native-roster adds to that model |
|
|
255
|
+
| --- | --- | --- |
|
|
256
|
+
| [react-native-calendar-kit](https://howljs.github.io/react-native-calendar-kit/) | Creation/editing gestures and pinch-to-zoom in a React Native calendar. | Layer-role coverage math and exact source sets on covered and removed spans. |
|
|
257
|
+
| [react-native-big-calendar](https://github.com/acro5piano/react-native-big-calendar) | Day, week, and month views of events. | A many-resource timeline and a single-lane schedule over the same layered interval contract. |
|
|
258
|
+
| [FullCalendar's resource timeline](https://fullcalendar.io/docs/timeline-view) | A web resource timeline with [dragging and resizing](https://fullcalendar.io/docs/event-dragging-resizing). | React Native components plus a renderer-free coverage/provenance core and explicit skipped/repeated wall-time geometry. |
|
|
259
|
+
|
|
260
|
+
These are differences in the supplied models, not claims that an alternative
|
|
261
|
+
cannot be extended. Their linked documentation describes their features; this
|
|
262
|
+
package's [contract](./src/core/types.ts), [coverage](./src/core/coverage.ts), and
|
|
263
|
+
[geometry](./docs/timezones.md) document its side. No relative performance
|
|
264
|
+
comparison has been recorded; see [performance evidence](./docs/performance.md).
|
|
265
|
+
|
|
593
266
|
## What this isn't
|
|
594
267
|
|
|
595
268
|
This is a read surface. Consumers own creation, dragging, resizing,
|
|
@@ -616,7 +289,7 @@ publint, size-limit, and Playwright.
|
|
|
616
289
|
- Area landing pages: [core](./src/core/README.md), [Roster](./src/components/roster/README.md),
|
|
617
290
|
[Schedule](./src/components/schedule/README.md), [layers](./src/components/layers/README.md),
|
|
618
291
|
[primitives](./src/components/primitives/README.md), [rrule adapter](./src/adapters/rrule/README.md),
|
|
619
|
-
[nativewind](./src/nativewind/README.md), the [gallery](./demo/components/gallery/README.md),
|
|
292
|
+
[nativewind](./src/nativewind/README.md), the [gallery](./demo/components/gallery/README.md), the
|
|
620
293
|
[showcase](./demo/components/team-roster/README.md), and the [theme](./demo/components/theme/README.md);
|
|
621
294
|
[adding an adapter](./docs/adding-an-adapter.md)
|
|
622
295
|
is the contributor recipe.
|
|
@@ -57,7 +57,7 @@ function RosterSelectionPopover({ anchorZone, contentZone, targetBounds, open, o
|
|
|
57
57
|
left: Math.max(0, Math.min((targetBounds?.x ?? 0) - x.get(), viewport.width - content.width)),
|
|
58
58
|
top: below >= 0 && below <= maxTop ? below : above >= 0 && above <= maxTop ? above : 0,
|
|
59
59
|
};
|
|
60
|
-
});
|
|
60
|
+
}, [targetBounds, content, viewport, x, y]);
|
|
61
61
|
(0, react_1.useEffect)(() => {
|
|
62
62
|
if (!open)
|
|
63
63
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection-layout.js","sourceRoot":"","sources":["../../../../../src/components/roster/selection/selection-layout.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iCAAmE;AACnE,+CAA6D;AAC7D,mFAAqE;AACrE,oDAA6D;AAG7D,MAAM,OAAO,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAW,CAAC;AAExF,mGAAmG;AACnG,gCAAuC,EACrC,UAAU,EACV,WAAW,EACX,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,UAAU,EACV,MAAM,GACe;IACrB,MAAM,IAAI,GAAG,IAAA,aAAK,GAAE,CAAC;IACrB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,IAAA,gBAAQ,EAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IACpD,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC;IACxB,MAAM,QAAQ,GAAG,IAAA,0CAAgB,EAAC,GAAG,EAAE;QACrC,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACpF,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7D,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC7F,GAAG,EAAE,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACvF,CAAC;IACJ,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"selection-layout.js","sourceRoot":"","sources":["../../../../../src/components/roster/selection/selection-layout.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-roster.d.ts","sourceRoot":"","sources":["../../../../src/components/roster/use-roster.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAoB,MAAM,gBAAgB,CAAC;AAIjF,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"use-roster.d.ts","sourceRoot":"","sources":["../../../../src/components/roster/use-roster.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAoB,MAAM,gBAAgB,CAAC;AAIjF,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CA2GzD"}
|
|
@@ -18,8 +18,10 @@ function useRoster(input) {
|
|
|
18
18
|
const [x] = (0, react_1.useState)(() => (0, react_native_reanimated_1.makeMutable)(0));
|
|
19
19
|
const [y] = (0, react_1.useState)(() => (0, react_native_reanimated_1.makeMutable)(0));
|
|
20
20
|
const bodyRef = (0, react_1.useRef)(null);
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// Explicit dependencies: a web bundler that skips Reanimated's Babel plugin
|
|
22
|
+
// for node_modules (Vite, Storybook) throws on a worklet without them.
|
|
23
|
+
const headerStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({ transform: [{ translateX: -x.get() }] }), [x]);
|
|
24
|
+
const labelStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({ transform: [{ translateY: -y.get() }] }), [y]);
|
|
23
25
|
const window = (0, core_1.windowFor)(windowSpec);
|
|
24
26
|
if (!(rowHeight > 0) ||
|
|
25
27
|
!Number.isFinite(rowHeight) ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-roster.js","sourceRoot":"","sources":["../../../../src/components/roster/use-roster.ts"],"names":[],"mappings":";;;AAAA,iCAAyC;AAEzC,qEAAwE;AAExE,qCAAoG;AAEpG,yDAA2E;AAC3E,yCAAyC;AAEzC,mBAA0B,KAAkB;IAC1C,MAAM,EACJ,KAAK,EACL,UAAU,EACV,GAAG,GAAG,IAAI,EACV,UAAU,GAAG,EAAE,EACf,SAAS,GAAG,cAAO,EACnB,SAAS,GAAG,EAAE,EACd,WAAW,GAAG,GAAG,EACjB,UAAU,GACX,GAAG,KAAK,CAAC;IACV,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAA0B,IAAI,CAAC,CAAC;IACxE,SAAS,gBAAgB;QACvB,WAAW,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAClE,0EAA0E;IAC1E,8EAA8E;IAC9E,MAAM,CAAC,CAAC,CAAC,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE,CAAC,IAAA,qCAAW,EAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,CAAC,CAAC,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE,CAAC,IAAA,qCAAW,EAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAA,cAAM,EAAa,IAAI,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,IAAA,0CAAgB,EAAC,GAAG,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"use-roster.js","sourceRoot":"","sources":["../../../../src/components/roster/use-roster.ts"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# react-native-roster
|
|
2
2
|
|
|
3
|
-
> React Native
|
|
3
|
+
> Resource timeline and schedule components for React Native and web, with layered intervals, coverage, provenance, and explicit daylight-saving handling.
|
|
4
4
|
> Roster projects many lanes horizontally; Schedule projects one lane into day
|
|
5
5
|
> columns. The core owns geometry, coverage, axis math, and caches. Adapters own
|
|
6
|
-
> upstream conversion and recurrence.
|
|
7
|
-
> the implemented surface; package distribution status is in README.md.
|
|
6
|
+
> upstream conversion and recurrence.
|
|
8
7
|
|
|
9
8
|
This file is for an agent integrating the package into a consumer app. Follow the
|
|
10
9
|
setup, contract, and cache rules below. You do not need the repository's issues
|
|
@@ -13,32 +12,32 @@ this repository. The demo is the working integration example, not a dependency.
|
|
|
13
12
|
|
|
14
13
|
## Setup and first render
|
|
15
14
|
|
|
16
|
-
Use Bun 1.4.2 and Node 22 for this checkout. Run at repository root:
|
|
17
|
-
|
|
18
15
|
```sh
|
|
19
|
-
bun
|
|
20
|
-
|
|
21
|
-
bun run demo:web
|
|
16
|
+
bun add react-native-roster @legendapp/list
|
|
17
|
+
bunx expo install react-native-reanimated --bun
|
|
22
18
|
```
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
`
|
|
20
|
+
For web, also install React DOM and React Native Web versions compatible with
|
|
21
|
+
your React/Expo setup, plus `bun add @radix-ui/react-popover@^1.1.23`.
|
|
22
|
+
Radix is required whenever a web bundler resolves the root entry or
|
|
23
|
+
`/nativewind` to the web selection layout, even with selection disabled or only
|
|
24
|
+
`Schedule` imported. That layout imports Radix unconditionally; the published
|
|
25
|
+
browser remap and Metro's web source resolution both reach it. Native and
|
|
26
|
+
`/core`-only or `/rrule`-only imports do not require Radix.
|
|
27
|
+
|
|
28
|
+
Peer ranges: React >=18.2, React Native >=0.74, LegendList >=2, Reanimated >=3.19,
|
|
29
|
+
Expo >=51 (optional), NativeWind >=4.1 (optional), and Radix ^1.1.23 (web).
|
|
30
|
+
Reanimated is required by `Roster` and optional for core-only use. Keep React,
|
|
31
|
+
React Native, and Expo aligned with your SDK. The demo uses Expo SDK 54,
|
|
32
|
+
React 19.1, React Native 0.81.5, LegendList 2.0.19, and Reanimated 3.19.5;
|
|
33
|
+
these versions do not establish support for every peer-range combination.
|
|
34
|
+
|
|
35
|
+
On Metro before React Native 0.79, enable `resolver.unstable_enablePackageExports`
|
|
36
|
+
so the subpaths resolve. Keep one React copy in workspaces.
|
|
37
|
+
`rrule-temporal` and `@js-temporal/polyfill` install as ordinary dependencies;
|
|
38
|
+
only `/rrule` imports them. Import isolation does not reduce installation size.
|
|
39
|
+
|
|
40
|
+
Save this as a screen component in your app:
|
|
42
41
|
|
|
43
42
|
```tsx
|
|
44
43
|
import { Roster } from 'react-native-roster';
|
|
@@ -61,8 +60,8 @@ export function Example() {
|
|
|
61
60
|
```
|
|
62
61
|
|
|
63
62
|
Expect a green 09:00 to 17:00 interval on January 1, 2024. Scroll horizontally to
|
|
64
|
-
see the time range. A bounded viewport is required.
|
|
65
|
-
|
|
63
|
+
see the time range. A bounded viewport is required. In a controlled app, own
|
|
64
|
+
WindowSpec in your screen's hook; update
|
|
66
65
|
it in navigation handlers, and prepare adapter output for windowFor(spec).
|
|
67
66
|
|
|
68
67
|
## Vocabulary
|
|
@@ -189,7 +188,7 @@ Component props are ComponentType<Input>.
|
|
|
189
188
|
|
|
190
189
|
Roster required props: lanes: Lane[], windowSpec: WindowSpec.
|
|
191
190
|
Optional: now (epoch ms or null, default null), minuteStep (60), rowHeight (48),
|
|
192
|
-
pxPerMinute (
|
|
191
|
+
pxPerMinute (default 0.5, positive finite, grows to fit viewport),
|
|
193
192
|
sortLanes (byLabel), highlightSource, onNavigate, onIntervalPress, onGapPress,
|
|
194
193
|
onCellPress, onIntervalHover (web only), style, headerStyle, laneLabelColumnStyle,
|
|
195
194
|
bodyStyle, laneLabelWidth (180), neverSetLabel, incompleteLabel, and zones below.
|
|
@@ -583,7 +582,7 @@ W: seed 1318, 200 lanes, week, two layers, 24-lane layout batch, 15-minute ticks
|
|
|
583
582
|
Bun 1.4.0, 2026-09-08T05:18:00Z, commit
|
|
584
583
|
1c4ac0e8580b1d46c043f946aa7ebc1ee316d0fa. Median of 11 target-cold samples after
|
|
585
584
|
five runtime warmups. Each timing must stay below 16 ms; CI also enforces <=1.5x
|
|
586
|
-
its baseline. Size gates: core <15 kB, root <
|
|
585
|
+
its baseline. Size gates: core <15 kB, root <52 kB minified/uncompressed, peers
|
|
587
586
|
external. Phone fps and cold-layout numbers are not yet captured; the paired
|
|
588
587
|
react-big-scheduler comparison is not yet measured. Do not claim relative speed.
|
|
589
588
|
|
|
@@ -591,9 +590,25 @@ The package does not create, drag, resize, persist, or authorize intervals; it
|
|
|
591
590
|
is not a general calendar. Consumers implement their own interactions. A rule
|
|
592
591
|
editor in the gallery is an integration example, not a rule-authoring product.
|
|
593
592
|
|
|
593
|
+
## Working from a checkout
|
|
594
|
+
|
|
595
|
+
Use Bun 1.4.2 and Node 22 for repository development. At the repository root:
|
|
596
|
+
|
|
597
|
+
```sh
|
|
598
|
+
bun install --ignore-scripts --frozen-lockfile
|
|
599
|
+
bun run build
|
|
600
|
+
bun run demo:web
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
The workspace install includes demo dependencies. Use `EXPO_OFFLINE=1 bun run
|
|
604
|
+
demo:web` if Expo cannot write its online dependency-validation cache. To consume
|
|
605
|
+
an unreleased checkout, run `bun pm pack --destination .cache`, then `bun add`
|
|
606
|
+
the printed absolute tarball path in your app.
|
|
607
|
+
|
|
594
608
|
## Reference
|
|
595
609
|
|
|
596
|
-
- [README](./README.md):
|
|
610
|
+
- [README](./README.md): published-package setup, quick start, positioning, and evidence.
|
|
611
|
+
- [Customization](./docs/customization.md): slot tables, selection layouts, and context recipe.
|
|
597
612
|
- [Adapter guide](./docs/adapters.md): table/feed conversion and memoization contract.
|
|
598
613
|
- Area landing pages: [core](./src/core/README.md), [Roster](./src/components/roster/README.md),
|
|
599
614
|
[Schedule](./src/components/schedule/README.md), [layers](./src/components/layers/README.md),
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-roster",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Resource timeline and schedule components for React Native and web, with layered intervals, coverage, provenance, and explicit daylight-saving handling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
7
|
+
"react-native-web",
|
|
7
8
|
"expo",
|
|
8
9
|
"roster",
|
|
9
|
-
"
|
|
10
|
+
"timeline",
|
|
11
|
+
"resource-timeline",
|
|
12
|
+
"schedule",
|
|
13
|
+
"resource-scheduling",
|
|
14
|
+
"coverage",
|
|
10
15
|
"intervals",
|
|
16
|
+
"timezone",
|
|
17
|
+
"dst",
|
|
18
|
+
"recurrence",
|
|
19
|
+
"rrule",
|
|
11
20
|
"provenance",
|
|
21
|
+
"typescript",
|
|
12
22
|
"nativewind"
|
|
13
23
|
],
|
|
14
24
|
"author": "Jesse Harlin (the-simian) <info@simiancraft.com>",
|
|
@@ -8,8 +8,9 @@ provenance per span.
|
|
|
8
8
|
- Exports: `expandRuleSet`, `envelopeFor`, `clearExpandCache`, `expandStats`,
|
|
9
9
|
`resetExpandStats`, and the `RosterRule`, `RosterDate`, `RuleSet`,
|
|
10
10
|
`ExpandOptions`, `ExpandResult`, and `ExpandStats` types
|
|
11
|
-
-
|
|
12
|
-
- Owns: `rrule-temporal` and the Temporal polyfill;
|
|
11
|
+
- Internal imports: `../../core` only; never components or another adapter
|
|
12
|
+
- Owns: `rrule-temporal` and the Temporal polyfill; ordinary installed dependencies
|
|
13
|
+
imported only by this subpath, never by root or core
|
|
13
14
|
|
|
14
15
|
## Supported input
|
|
15
16
|
|
|
@@ -26,7 +27,7 @@ unbounded strings are rejected by validation.
|
|
|
26
27
|
| `index.ts` | the entry point; the only barrel |
|
|
27
28
|
| `types.ts` | input and result types |
|
|
28
29
|
| `validate.ts` | input rejection before any engine work |
|
|
29
|
-
| `envelope.ts` |
|
|
30
|
+
| `envelope.ts` | pads both absolute window bounds by 48 hours, shared across rule zones |
|
|
30
31
|
| `occurrences.ts` | drives the engine and applies every calendar correction |
|
|
31
32
|
| `net.ts` | nets includes against excludes into intervals and gaps |
|
|
32
33
|
| `cache.ts`, `hash.ts` | retained occurrences and envelopes keyed by content |
|
|
@@ -8,7 +8,7 @@ projections draw the same components, so they live above Roster and Schedule.
|
|
|
8
8
|
- `parts/interval.tsx`, `parts/gap.tsx`: the default components
|
|
9
9
|
- `utils/styles.ts`: content-keyed style objects per layer
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Internal imports stay within layers and core; the components also import React Native. Tests: `test/components/layers`.
|
|
12
12
|
|
|
13
13
|
Both projections accept intervalComponent and gapComponent as ComponentType inputs.
|
|
14
14
|
Their data-owning lane or column mounts the component with the final rect, layer,
|
|
@@ -17,8 +17,8 @@ This feature is about a roster; its children are lanes.
|
|
|
17
17
|
- `utils/`: ticks and the body content key
|
|
18
18
|
|
|
19
19
|
Interval and gap components live in `../layers`; press geometry and `regionStyle`
|
|
20
|
-
in `../primitives`.
|
|
21
|
-
|
|
20
|
+
in `../primitives`. See the [slot tables, selection contract, and context recipe](../../../docs/customization.md#roster-zones)
|
|
21
|
+
and `llms.txt` for integration details. Tests: `test/components/roster`.
|
|
22
22
|
|
|
23
23
|
This feature is about a roster body; its children are lanes. `RosterBody` gates
|
|
24
24
|
measurement and composes the body layout and lane list. `BodyInput` extends the
|
|
@@ -31,7 +31,7 @@ export function RosterSelectionPopover({
|
|
|
31
31
|
left: Math.max(0, Math.min((targetBounds?.x ?? 0) - x.get(), viewport.width - content.width)),
|
|
32
32
|
top: below >= 0 && below <= maxTop ? below : above >= 0 && above <= maxTop ? above : 0,
|
|
33
33
|
};
|
|
34
|
-
});
|
|
34
|
+
}, [targetBounds, content, viewport, x, y]);
|
|
35
35
|
useEffect(() => {
|
|
36
36
|
if (!open) return;
|
|
37
37
|
const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
|
|
@@ -28,8 +28,10 @@ export function useRoster(input: RosterInput): RosterModel {
|
|
|
28
28
|
const [x] = useState(() => makeMutable(0));
|
|
29
29
|
const [y] = useState(() => makeMutable(0));
|
|
30
30
|
const bodyRef = useRef<ScrollView>(null);
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// Explicit dependencies: a web bundler that skips Reanimated's Babel plugin
|
|
32
|
+
// for node_modules (Vite, Storybook) throws on a worklet without them.
|
|
33
|
+
const headerStyle = useAnimatedStyle(() => ({ transform: [{ translateX: -x.get() }] }), [x]);
|
|
34
|
+
const labelStyle = useAnimatedStyle(() => ({ transform: [{ translateY: -y.get() }] }), [y]);
|
|
33
35
|
const window = windowFor(windowSpec);
|
|
34
36
|
if (
|
|
35
37
|
!(rowHeight > 0) ||
|
|
@@ -14,8 +14,8 @@ This feature is about a schedule; its children are days.
|
|
|
14
14
|
- `utils/days.ts`: header dates, now position, and transition bounds
|
|
15
15
|
|
|
16
16
|
Interval and gap components live in `../layers`; press geometry and `regionStyle`
|
|
17
|
-
in `../primitives`. Zone contracts
|
|
18
|
-
|
|
17
|
+
in `../primitives`. Zone contracts and a complete example live in the
|
|
18
|
+
[customization guide](../../../docs/customization.md#schedule-zones) and `llms.txt`. Tests: `test/components/schedule`.
|
|
19
19
|
|
|
20
20
|
The chassis binds default component types once; ScheduleDay mounts components
|
|
21
21
|
with day data and passes nodes into ScheduleDayLayout. There is no black-box day
|
package/src/core/README.md
CHANGED
|
@@ -40,3 +40,46 @@ Reference: [design](../../docs/design.md), [timezones](../../docs/timezones.md),
|
|
|
40
40
|
|
|
41
41
|
The public entry binds immutable function aliases directly to preserve implementation
|
|
42
42
|
identity while avoiding repeated CommonJS getter wrappers in consumer bundles.
|
|
43
|
+
|
|
44
|
+
## Interval helper walkthrough
|
|
45
|
+
|
|
46
|
+
`unionOf(spans: readonly Window[]): Window[]` merges overlapping or touching
|
|
47
|
+
windows and returns them sorted by start, keeping disjoint windows separate.
|
|
48
|
+
Empty input returns `[]`. Every returned window is a new object; the input array
|
|
49
|
+
and its windows remain untouched. Like `extentOf` and `intersectionOf`, it uses
|
|
50
|
+
end-exclusive epoch milliseconds and throws `RangeError` for non-finite bounds
|
|
51
|
+
or `end <= start`.
|
|
52
|
+
|
|
53
|
+
Collapse each person's segments with `extentOf`, then use `intersectionOf` to
|
|
54
|
+
find the shared time across those windows. Each extent bridges gaps between
|
|
55
|
+
segments, so the result describes the collapsed windows.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import type { Window } from 'react-native-roster/core';
|
|
59
|
+
import { extentOf, intersectionOf } from 'react-native-roster/core';
|
|
60
|
+
|
|
61
|
+
const people: { name: string; segments: Window[] }[] = [
|
|
62
|
+
{
|
|
63
|
+
name: 'Alex',
|
|
64
|
+
segments: [
|
|
65
|
+
{ start: 1_000, end: 3_000 },
|
|
66
|
+
{ start: 4_000, end: 8_000 },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'Sam',
|
|
71
|
+
segments: [
|
|
72
|
+
{ start: 2_000, end: 5_000 },
|
|
73
|
+
{ start: 6_000, end: 9_000 },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const windows = people.map((person): Window => {
|
|
79
|
+
const extent = extentOf(person.segments);
|
|
80
|
+
if (extent === null) throw new Error(`${person.name} has no segments`);
|
|
81
|
+
return extent;
|
|
82
|
+
});
|
|
83
|
+
const sharedTime = intersectionOf(windows); // { start: 2_000, end: 8_000 }
|
|
84
|
+
// A null result from intersectionOf means there is no shared time.
|
|
85
|
+
```
|
package/src/nativewind/README.md
CHANGED
|
@@ -10,5 +10,49 @@ Registers `Roster` and `Schedule` with NativeWind's `cssInterop` so each
|
|
|
10
10
|
|
|
11
11
|
Class props win over default paint because layouts compose regions with
|
|
12
12
|
`regionStyle`, which drops the default when the override carries a class entry.
|
|
13
|
-
Fine detail is styled through zones. See
|
|
13
|
+
Fine detail is styled through zones. See [setup and styling](#setup-and-styling).
|
|
14
|
+
Web imports also require Radix through Roster; see [Install](../../README.md#install).
|
|
14
15
|
Tests: `test/package/nativewind-entry.test.ts`.
|
|
16
|
+
|
|
17
|
+
## Setup and styling
|
|
18
|
+
|
|
19
|
+
`className` is a first-class prop on `Roster` and `Schedule`, alongside `style`.
|
|
20
|
+
Every chrome style prop has a class twin: `className`, `headerClassName`,
|
|
21
|
+
`laneLabelColumnClassName`, and `bodyClassName` on Roster; `className`,
|
|
22
|
+
`headerClassName`, `gutterClassName`, and `daysClassName` on Schedule. A class
|
|
23
|
+
on a region replaces that region's default paint (background and border colors)
|
|
24
|
+
while its structure (size, flex, overflow) stays. This is deliberate: React
|
|
25
|
+
Native Web renders object styles inline, and inline paint would otherwise beat
|
|
26
|
+
any class.
|
|
27
|
+
|
|
28
|
+
Set up NativeWind 4 as usual (the demo's [babel.config.js](../../demo/babel.config.js),
|
|
29
|
+
[metro.config.js](../../demo/metro.config.js), [tailwind.config.js](../../demo/tailwind.config.js),
|
|
30
|
+
and [global.css](../../demo/global.css) are a working reference), add the optional
|
|
31
|
+
peer with `bun add nativewind`, and register the components once at your app
|
|
32
|
+
root:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// app/_layout.tsx
|
|
36
|
+
import '../global.css';
|
|
37
|
+
import 'react-native-roster/nativewind';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { Roster } from 'react-native-roster';
|
|
42
|
+
|
|
43
|
+
<Roster
|
|
44
|
+
lanes={lanes}
|
|
45
|
+
windowSpec={windowSpec}
|
|
46
|
+
className="flex-1 rounded-xl bg-background"
|
|
47
|
+
headerClassName="border-b border-border bg-card"
|
|
48
|
+
laneLabelColumnClassName="border-r border-border bg-card"
|
|
49
|
+
laneLabelWidth={220}
|
|
50
|
+
/>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Slot components are ordinary React Native views, so a custom `laneLabelComponent` or
|
|
54
|
+
`intervalComponent` uses `className` on `View` and `Text` directly. Without the
|
|
55
|
+
entry point, class props are ignored and style props still work. The entry
|
|
56
|
+
point is the package's only module with side effects and is listed in
|
|
57
|
+
`sideEffects`. The [showcase route](../../demo/app/showcase.tsx) styles every region
|
|
58
|
+
and zone this way in light and dark palettes.
|