react-native-roster 0.0.0 → 0.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/NOTICE.md +29 -0
- package/README.md +378 -2
- package/dist/src/adapters/rrule/cache.d.ts +15 -0
- package/dist/src/adapters/rrule/cache.d.ts.map +1 -0
- package/dist/src/adapters/rrule/cache.js +53 -0
- package/dist/src/adapters/rrule/cache.js.map +1 -0
- package/dist/src/adapters/rrule/envelope.d.ts +3 -0
- package/dist/src/adapters/rrule/envelope.d.ts.map +1 -0
- package/dist/src/adapters/rrule/envelope.js +9 -0
- package/dist/src/adapters/rrule/envelope.js.map +1 -0
- package/dist/src/adapters/rrule/expand.d.ts +4 -0
- package/dist/src/adapters/rrule/expand.d.ts.map +1 -0
- package/dist/src/adapters/rrule/expand.js +92 -0
- package/dist/src/adapters/rrule/expand.js.map +1 -0
- package/dist/src/adapters/rrule/hash.d.ts +3 -0
- package/dist/src/adapters/rrule/hash.d.ts.map +1 -0
- package/dist/src/adapters/rrule/hash.js +15 -0
- package/dist/src/adapters/rrule/hash.js.map +1 -0
- package/dist/src/adapters/rrule/index.d.ts +5 -0
- package/dist/src/adapters/rrule/index.d.ts.map +1 -0
- package/dist/src/adapters/rrule/index.js +12 -0
- package/dist/src/adapters/rrule/index.js.map +1 -0
- package/dist/src/adapters/rrule/net.d.ts +6 -0
- package/dist/src/adapters/rrule/net.d.ts.map +1 -0
- package/dist/src/adapters/rrule/net.js +31 -0
- package/dist/src/adapters/rrule/net.js.map +1 -0
- package/dist/src/adapters/rrule/occurrences.d.ts +6 -0
- package/dist/src/adapters/rrule/occurrences.d.ts.map +1 -0
- package/dist/src/adapters/rrule/occurrences.js +252 -0
- package/dist/src/adapters/rrule/occurrences.js.map +1 -0
- package/dist/src/adapters/rrule/types.d.ts +62 -0
- package/dist/src/adapters/rrule/types.d.ts.map +1 -0
- package/dist/src/adapters/rrule/types.js +3 -0
- package/dist/src/adapters/rrule/types.js.map +1 -0
- package/dist/src/adapters/rrule/validate.d.ts +6 -0
- package/dist/src/adapters/rrule/validate.d.ts.map +1 -0
- package/dist/src/adapters/rrule/validate.js +64 -0
- package/dist/src/adapters/rrule/validate.js.map +1 -0
- package/dist/src/components/layers/layers.types.d.ts +15 -0
- package/dist/src/components/layers/layers.types.d.ts.map +1 -0
- package/dist/src/components/layers/layers.types.js +3 -0
- package/dist/src/components/layers/layers.types.js.map +1 -0
- package/dist/src/components/layers/parts/gap.d.ts +3 -0
- package/dist/src/components/layers/parts/gap.d.ts.map +1 -0
- package/dist/src/components/layers/parts/gap.js +7 -0
- package/dist/src/components/layers/parts/gap.js.map +1 -0
- package/dist/src/components/layers/parts/interval.d.ts +3 -0
- package/dist/src/components/layers/parts/interval.d.ts.map +1 -0
- package/dist/src/components/layers/parts/interval.js +22 -0
- package/dist/src/components/layers/parts/interval.js.map +1 -0
- package/dist/src/components/layers/utils/styles.d.ts +7 -0
- package/dist/src/components/layers/utils/styles.d.ts.map +1 -0
- package/dist/src/components/layers/utils/styles.js +24 -0
- package/dist/src/components/layers/utils/styles.js.map +1 -0
- package/dist/src/components/primitives/press-point.d.ts +3 -0
- package/dist/src/components/primitives/press-point.d.ts.map +1 -0
- package/dist/src/components/primitives/press-point.js +7 -0
- package/dist/src/components/primitives/press-point.js.map +1 -0
- package/dist/src/components/primitives/press-point.types.d.ts +14 -0
- package/dist/src/components/primitives/press-point.types.d.ts.map +1 -0
- package/dist/src/components/primitives/press-point.types.js +3 -0
- package/dist/src/components/primitives/press-point.types.js.map +1 -0
- package/dist/src/components/primitives/press-point.web.d.ts +3 -0
- package/dist/src/components/primitives/press-point.web.d.ts.map +1 -0
- package/dist/src/components/primitives/press-point.web.js +17 -0
- package/dist/src/components/primitives/press-point.web.js.map +1 -0
- package/dist/src/components/primitives/region-style.d.ts +9 -0
- package/dist/src/components/primitives/region-style.d.ts.map +1 -0
- package/dist/src/components/primitives/region-style.js +20 -0
- package/dist/src/components/primitives/region-style.js.map +1 -0
- package/dist/src/components/roster/index.d.ts +3 -0
- package/dist/src/components/roster/index.d.ts.map +1 -0
- package/dist/src/components/roster/index.js +56 -0
- package/dist/src/components/roster/index.js.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.d.ts +3 -0
- package/dist/src/components/roster/lanes/interval-hover.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.js +7 -0
- package/dist/src/components/roster/lanes/interval-hover.js.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.types.d.ts +15 -0
- package/dist/src/components/roster/lanes/interval-hover.types.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.types.js +3 -0
- package/dist/src/components/roster/lanes/interval-hover.types.js.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.web.d.ts +3 -0
- package/dist/src/components/roster/lanes/interval-hover.web.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.web.js +20 -0
- package/dist/src/components/roster/lanes/interval-hover.web.js.map +1 -0
- package/dist/src/components/roster/lanes/lane.d.ts +10 -0
- package/dist/src/components/roster/lanes/lane.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/lane.js +37 -0
- package/dist/src/components/roster/lanes/lane.js.map +1 -0
- package/dist/src/components/roster/lanes/parts/incomplete.d.ts +8 -0
- package/dist/src/components/roster/lanes/parts/incomplete.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/parts/incomplete.js +30 -0
- package/dist/src/components/roster/lanes/parts/incomplete.js.map +1 -0
- package/dist/src/components/roster/lanes/parts/lane-label.d.ts +3 -0
- package/dist/src/components/roster/lanes/parts/lane-label.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/parts/lane-label.js +18 -0
- package/dist/src/components/roster/lanes/parts/lane-label.js.map +1 -0
- package/dist/src/components/roster/layout.d.ts +17 -0
- package/dist/src/components/roster/layout.d.ts.map +1 -0
- package/dist/src/components/roster/layout.js +24 -0
- package/dist/src/components/roster/layout.js.map +1 -0
- package/dist/src/components/roster/parts/body.d.ts +7 -0
- package/dist/src/components/roster/parts/body.d.ts.map +1 -0
- package/dist/src/components/roster/parts/body.js +29 -0
- package/dist/src/components/roster/parts/body.js.map +1 -0
- package/dist/src/components/roster/parts/corner.d.ts +2 -0
- package/dist/src/components/roster/parts/corner.d.ts.map +1 -0
- package/dist/src/components/roster/parts/corner.js +7 -0
- package/dist/src/components/roster/parts/corner.js.map +1 -0
- package/dist/src/components/roster/parts/empty.d.ts +2 -0
- package/dist/src/components/roster/parts/empty.d.ts.map +1 -0
- package/dist/src/components/roster/parts/empty.js +9 -0
- package/dist/src/components/roster/parts/empty.js.map +1 -0
- package/dist/src/components/roster/parts/grid.d.ts +3 -0
- package/dist/src/components/roster/parts/grid.d.ts.map +1 -0
- package/dist/src/components/roster/parts/grid.js +16 -0
- package/dist/src/components/roster/parts/grid.js.map +1 -0
- package/dist/src/components/roster/parts/header-cell.d.ts +5 -0
- package/dist/src/components/roster/parts/header-cell.d.ts.map +1 -0
- package/dist/src/components/roster/parts/header-cell.js +9 -0
- package/dist/src/components/roster/parts/header-cell.js.map +1 -0
- package/dist/src/components/roster/parts/header.d.ts +3 -0
- package/dist/src/components/roster/parts/header.d.ts.map +1 -0
- package/dist/src/components/roster/parts/header.js +13 -0
- package/dist/src/components/roster/parts/header.js.map +1 -0
- package/dist/src/components/roster/parts/lane-label-column.d.ts +3 -0
- package/dist/src/components/roster/parts/lane-label-column.d.ts.map +1 -0
- package/dist/src/components/roster/parts/lane-label-column.js +18 -0
- package/dist/src/components/roster/parts/lane-label-column.js.map +1 -0
- package/dist/src/components/roster/roster.types.d.ts +146 -0
- package/dist/src/components/roster/roster.types.d.ts.map +1 -0
- package/dist/src/components/roster/roster.types.js +3 -0
- package/dist/src/components/roster/roster.types.js.map +1 -0
- package/dist/src/components/roster/use-roster.d.ts +3 -0
- package/dist/src/components/roster/use-roster.d.ts.map +1 -0
- package/dist/src/components/roster/use-roster.js +106 -0
- package/dist/src/components/roster/use-roster.js.map +1 -0
- package/dist/src/components/roster/utils/body-content-key.d.ts +3 -0
- package/dist/src/components/roster/utils/body-content-key.d.ts.map +1 -0
- package/dist/src/components/roster/utils/body-content-key.js +29 -0
- package/dist/src/components/roster/utils/body-content-key.js.map +1 -0
- package/dist/src/components/roster/utils/ticks.d.ts +4 -0
- package/dist/src/components/roster/utils/ticks.d.ts.map +1 -0
- package/dist/src/components/roster/utils/ticks.js +60 -0
- package/dist/src/components/roster/utils/ticks.js.map +1 -0
- package/dist/src/components/schedule/days/day.d.ts +20 -0
- package/dist/src/components/schedule/days/day.d.ts.map +1 -0
- package/dist/src/components/schedule/days/day.js +26 -0
- package/dist/src/components/schedule/days/day.js.map +1 -0
- package/dist/src/components/schedule/days/header-layout.d.ts +9 -0
- package/dist/src/components/schedule/days/header-layout.d.ts.map +1 -0
- package/dist/src/components/schedule/days/header-layout.js +9 -0
- package/dist/src/components/schedule/days/header-layout.js.map +1 -0
- package/dist/src/components/schedule/days/layout.d.ts +18 -0
- package/dist/src/components/schedule/days/layout.d.ts.map +1 -0
- package/dist/src/components/schedule/days/layout.js +9 -0
- package/dist/src/components/schedule/days/layout.js.map +1 -0
- package/dist/src/components/schedule/days/parts/column.d.ts +3 -0
- package/dist/src/components/schedule/days/parts/column.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/column.js +33 -0
- package/dist/src/components/schedule/days/parts/column.js.map +1 -0
- package/dist/src/components/schedule/days/parts/day-header.d.ts +5 -0
- package/dist/src/components/schedule/days/parts/day-header.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/day-header.js +10 -0
- package/dist/src/components/schedule/days/parts/day-header.js.map +1 -0
- package/dist/src/components/schedule/days/parts/grid.d.ts +3 -0
- package/dist/src/components/schedule/days/parts/grid.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/grid.js +14 -0
- package/dist/src/components/schedule/days/parts/grid.js.map +1 -0
- package/dist/src/components/schedule/days/parts/now-line.d.ts +5 -0
- package/dist/src/components/schedule/days/parts/now-line.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/now-line.js +9 -0
- package/dist/src/components/schedule/days/parts/now-line.js.map +1 -0
- package/dist/src/components/schedule/days/parts/skipped-date.d.ts +4 -0
- package/dist/src/components/schedule/days/parts/skipped-date.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/skipped-date.js +17 -0
- package/dist/src/components/schedule/days/parts/skipped-date.js.map +1 -0
- package/dist/src/components/schedule/days/parts/transition.d.ts +3 -0
- package/dist/src/components/schedule/days/parts/transition.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/transition.js +42 -0
- package/dist/src/components/schedule/days/parts/transition.js.map +1 -0
- package/dist/src/components/schedule/index.d.ts +3 -0
- package/dist/src/components/schedule/index.d.ts.map +1 -0
- package/dist/src/components/schedule/index.js +45 -0
- package/dist/src/components/schedule/index.js.map +1 -0
- package/dist/src/components/schedule/layout.d.ts +15 -0
- package/dist/src/components/schedule/layout.d.ts.map +1 -0
- package/dist/src/components/schedule/layout.js +10 -0
- package/dist/src/components/schedule/layout.js.map +1 -0
- package/dist/src/components/schedule/parts/gutter.d.ts +3 -0
- package/dist/src/components/schedule/parts/gutter.d.ts.map +1 -0
- package/dist/src/components/schedule/parts/gutter.js +9 -0
- package/dist/src/components/schedule/parts/gutter.js.map +1 -0
- package/dist/src/components/schedule/parts/incomplete.d.ts +6 -0
- package/dist/src/components/schedule/parts/incomplete.d.ts.map +1 -0
- package/dist/src/components/schedule/parts/incomplete.js +9 -0
- package/dist/src/components/schedule/parts/incomplete.js.map +1 -0
- package/dist/src/components/schedule/schedule.types.d.ts +109 -0
- package/dist/src/components/schedule/schedule.types.d.ts.map +1 -0
- package/dist/src/components/schedule/schedule.types.js +3 -0
- package/dist/src/components/schedule/schedule.types.js.map +1 -0
- package/dist/src/components/schedule/use-schedule-viewport.d.ts +7 -0
- package/dist/src/components/schedule/use-schedule-viewport.d.ts.map +1 -0
- package/dist/src/components/schedule/use-schedule-viewport.js +15 -0
- package/dist/src/components/schedule/use-schedule-viewport.js.map +1 -0
- package/dist/src/components/schedule/use-schedule.d.ts +3 -0
- package/dist/src/components/schedule/use-schedule.d.ts.map +1 -0
- package/dist/src/components/schedule/use-schedule.js +71 -0
- package/dist/src/components/schedule/use-schedule.js.map +1 -0
- package/dist/src/components/schedule/utils/days.d.ts +17 -0
- package/dist/src/components/schedule/utils/days.d.ts.map +1 -0
- package/dist/src/components/schedule/utils/days.js +49 -0
- package/dist/src/components/schedule/utils/days.js.map +1 -0
- package/dist/src/core/axis.d.ts +18 -0
- package/dist/src/core/axis.d.ts.map +1 -0
- package/dist/src/core/axis.js +58 -0
- package/dist/src/core/axis.js.map +1 -0
- package/dist/src/core/cache.d.ts +29 -0
- package/dist/src/core/cache.d.ts.map +1 -0
- package/dist/src/core/cache.js +33 -0
- package/dist/src/core/cache.js.map +1 -0
- package/dist/src/core/columns.d.ts +3 -0
- package/dist/src/core/columns.d.ts.map +1 -0
- package/dist/src/core/columns.js +45 -0
- package/dist/src/core/columns.js.map +1 -0
- package/dist/src/core/coverage.d.ts +3 -0
- package/dist/src/core/coverage.d.ts.map +1 -0
- package/dist/src/core/coverage.js +60 -0
- package/dist/src/core/coverage.js.map +1 -0
- package/dist/src/core/flag.d.ts +3 -0
- package/dist/src/core/flag.d.ts.map +1 -0
- package/dist/src/core/flag.js +17 -0
- package/dist/src/core/flag.js.map +1 -0
- package/dist/src/core/hash.d.ts +5 -0
- package/dist/src/core/hash.d.ts.map +1 -0
- package/dist/src/core/hash.js +29 -0
- package/dist/src/core/hash.js.map +1 -0
- package/dist/src/core/hit-test.d.ts +6 -0
- package/dist/src/core/hit-test.d.ts.map +1 -0
- package/dist/src/core/hit-test.js +23 -0
- package/dist/src/core/hit-test.js.map +1 -0
- package/dist/src/core/index.d.ts +12 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +30 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/layout.d.ts +3 -0
- package/dist/src/core/layout.d.ts.map +1 -0
- package/dist/src/core/layout.js +96 -0
- package/dist/src/core/layout.js.map +1 -0
- package/dist/src/core/order.d.ts +7 -0
- package/dist/src/core/order.d.ts.map +1 -0
- package/dist/src/core/order.js +12 -0
- package/dist/src/core/order.js.map +1 -0
- package/dist/src/core/scale.d.ts +9 -0
- package/dist/src/core/scale.d.ts.map +1 -0
- package/dist/src/core/scale.js +41 -0
- package/dist/src/core/scale.js.map +1 -0
- package/dist/src/core/snap.d.ts +10 -0
- package/dist/src/core/snap.d.ts.map +1 -0
- package/dist/src/core/snap.js +43 -0
- package/dist/src/core/snap.js.map +1 -0
- package/dist/src/core/spans.d.ts +3 -0
- package/dist/src/core/spans.d.ts.map +1 -0
- package/dist/src/core/spans.js +60 -0
- package/dist/src/core/spans.js.map +1 -0
- package/dist/src/core/types.d.ts +104 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +3 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/zone.d.ts +10 -0
- package/dist/src/core/zone.d.ts.map +1 -0
- package/dist/src/core/zone.js +103 -0
- package/dist/src/core/zone.js.map +1 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +63 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/nativewind/index.d.ts +61 -0
- package/dist/src/nativewind/index.d.ts.map +1 -0
- package/dist/src/nativewind/index.js +24 -0
- package/dist/src/nativewind/index.js.map +1 -0
- package/llms.txt +481 -0
- package/package.json +154 -8
- package/src/adapters/rrule/README.md +41 -0
- package/src/adapters/rrule/cache.ts +50 -0
- package/src/adapters/rrule/envelope.ts +7 -0
- package/src/adapters/rrule/expand.ts +105 -0
- package/src/adapters/rrule/hash.ts +13 -0
- package/src/adapters/rrule/index.ts +11 -0
- package/src/adapters/rrule/net.ts +30 -0
- package/src/adapters/rrule/occurrences.ts +288 -0
- package/src/adapters/rrule/types.ts +52 -0
- package/src/adapters/rrule/validate.ts +78 -0
- package/src/components/layers/README.md +11 -0
- package/src/components/layers/layers.types.ts +6 -0
- package/src/components/layers/parts/gap.tsx +5 -0
- package/src/components/layers/parts/interval.tsx +24 -0
- package/src/components/layers/utils/styles.ts +24 -0
- package/src/components/primitives/README.md +12 -0
- package/src/components/primitives/press-point.tsx +5 -0
- package/src/components/primitives/press-point.types.ts +5 -0
- package/src/components/primitives/press-point.web.tsx +19 -0
- package/src/components/primitives/region-style.ts +21 -0
- package/src/components/roster/README.md +18 -0
- package/src/components/roster/index.tsx +90 -0
- package/src/components/roster/lanes/interval-hover.tsx +5 -0
- package/src/components/roster/lanes/interval-hover.types.ts +11 -0
- package/src/components/roster/lanes/interval-hover.web.tsx +19 -0
- package/src/components/roster/lanes/lane.tsx +88 -0
- package/src/components/roster/lanes/parts/incomplete.tsx +44 -0
- package/src/components/roster/lanes/parts/lane-label.tsx +41 -0
- package/src/components/roster/layout.tsx +68 -0
- package/src/components/roster/parts/body.tsx +91 -0
- package/src/components/roster/parts/corner.tsx +3 -0
- package/src/components/roster/parts/empty.tsx +5 -0
- package/src/components/roster/parts/grid.tsx +26 -0
- package/src/components/roster/parts/header-cell.tsx +10 -0
- package/src/components/roster/parts/header.tsx +18 -0
- package/src/components/roster/parts/lane-label-column.tsx +31 -0
- package/src/components/roster/roster.types.ts +155 -0
- package/src/components/roster/use-roster.ts +133 -0
- package/src/components/roster/utils/body-content-key.ts +46 -0
- package/src/components/roster/utils/ticks.ts +62 -0
- package/src/components/schedule/README.md +18 -0
- package/src/components/schedule/days/day.tsx +76 -0
- package/src/components/schedule/days/header-layout.tsx +12 -0
- package/src/components/schedule/days/layout.tsx +38 -0
- package/src/components/schedule/days/parts/column.tsx +59 -0
- package/src/components/schedule/days/parts/day-header.tsx +18 -0
- package/src/components/schedule/days/parts/grid.tsx +21 -0
- package/src/components/schedule/days/parts/now-line.tsx +11 -0
- package/src/components/schedule/days/parts/skipped-date.tsx +22 -0
- package/src/components/schedule/days/parts/transition.tsx +70 -0
- package/src/components/schedule/index.tsx +109 -0
- package/src/components/schedule/layout.tsx +42 -0
- package/src/components/schedule/parts/gutter.tsx +16 -0
- package/src/components/schedule/parts/incomplete.tsx +13 -0
- package/src/components/schedule/schedule.types.ts +103 -0
- package/src/components/schedule/use-schedule-viewport.ts +13 -0
- package/src/components/schedule/use-schedule.ts +75 -0
- package/src/components/schedule/utils/days.ts +54 -0
- package/src/core/README.md +25 -0
- package/src/core/axis.ts +58 -0
- package/src/core/cache.ts +38 -0
- package/src/core/columns.ts +49 -0
- package/src/core/coverage.ts +56 -0
- package/src/core/flag.ts +14 -0
- package/src/core/hash.ts +28 -0
- package/src/core/hit-test.ts +27 -0
- package/src/core/index.ts +34 -0
- package/src/core/layout.ts +101 -0
- package/src/core/order.ts +18 -0
- package/src/core/scale.ts +45 -0
- package/src/core/snap.ts +52 -0
- package/src/core/spans.ts +58 -0
- package/src/core/types.ts +92 -0
- package/src/core/zone.ts +100 -0
- package/src/index.ts +49 -0
- package/src/nativewind/README.md +14 -0
- package/src/nativewind/index.ts +30 -0
- package/index.js +0 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# layers
|
|
2
|
+
|
|
3
|
+
This area is about a layer; its children are intervals and gaps. Both
|
|
4
|
+
projections draw the same fillers, so they live above Roster and Schedule.
|
|
5
|
+
|
|
6
|
+
- Root entry exports: `RosterInterval`, `RosterGap`, `IntervalInput`, `GapInput`
|
|
7
|
+
- `layers.types.ts`: the filler input contract both projections pass
|
|
8
|
+
- `parts/interval.tsx`, `parts/gap.tsx`: the default fillers
|
|
9
|
+
- `utils/styles.ts`: content-keyed style objects per layer
|
|
10
|
+
|
|
11
|
+
Imports core only. Tests: `test/components/layers`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Lane, Layer, Rect } from '../../core';
|
|
2
|
+
|
|
3
|
+
/** One covered span of a layer, positioned by the projection; `highlighted` follows the active source. */
|
|
4
|
+
export type IntervalInput = { rect: Rect; layer: Layer; lane: Lane; highlighted: boolean };
|
|
5
|
+
/** One removed span of a layer, positioned by the projection. */
|
|
6
|
+
export type GapInput = { rect: Rect; layer: Layer; lane: Lane };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import type { IntervalInput } from '../layers.types';
|
|
3
|
+
import { stylesFor } from '../utils/styles';
|
|
4
|
+
|
|
5
|
+
export function RosterInterval({ rect, layer, highlighted }: IntervalInput) {
|
|
6
|
+
const styles = stylesFor(layer);
|
|
7
|
+
const fill = highlighted ? styles.highlighted : styles.normal;
|
|
8
|
+
return (
|
|
9
|
+
<View
|
|
10
|
+
pointerEvents="none"
|
|
11
|
+
style={[
|
|
12
|
+
fill,
|
|
13
|
+
{
|
|
14
|
+
position: 'absolute',
|
|
15
|
+
left: rect.x,
|
|
16
|
+
top: rect.y,
|
|
17
|
+
width: rect.width,
|
|
18
|
+
height: rect.height,
|
|
19
|
+
zIndex: rect.z,
|
|
20
|
+
},
|
|
21
|
+
]}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import type { Layer } from '../../../core';
|
|
3
|
+
import { structuralKey } from '../../../core/hash';
|
|
4
|
+
|
|
5
|
+
const styles = new Map<string, { normal: ViewStyle; highlighted: ViewStyle }>();
|
|
6
|
+
|
|
7
|
+
export function stylesFor(layer: Layer): { normal: ViewStyle; highlighted: ViewStyle } {
|
|
8
|
+
const key = structuralKey([layer.id, layer.style]);
|
|
9
|
+
let result = styles.get(key);
|
|
10
|
+
if (!result) {
|
|
11
|
+
const normal: ViewStyle = {
|
|
12
|
+
backgroundColor: layer.style.color,
|
|
13
|
+
opacity: layer.style.opacity ?? 1,
|
|
14
|
+
width: '100%',
|
|
15
|
+
height: '100%',
|
|
16
|
+
};
|
|
17
|
+
result = {
|
|
18
|
+
normal,
|
|
19
|
+
highlighted: { ...normal, backgroundColor: layer.style.highlightColor ?? layer.style.color },
|
|
20
|
+
};
|
|
21
|
+
styles.set(key, result);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# primitives
|
|
2
|
+
|
|
3
|
+
Hookless building blocks shared by Roster and Schedule. Nothing here is a
|
|
4
|
+
public export.
|
|
5
|
+
|
|
6
|
+
- `press-point.tsx`, `press-point.web.tsx`, `press-point.types.ts`: pointer
|
|
7
|
+
coordinates relative to the pressed surface; the web file is selected by the
|
|
8
|
+
`browser` remap in package.json
|
|
9
|
+
- `region-style.ts`: `regionStyle(structure, paint, override)`, which drops the
|
|
10
|
+
default paint when the override carries a NativeWind class entry
|
|
11
|
+
|
|
12
|
+
Tests: `test/components/primitives`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PressPoint, PressPointInput } from './press-point.types';
|
|
2
|
+
|
|
3
|
+
export function pressPoint({ nativeEvent, currentTarget }: PressPointInput): PressPoint {
|
|
4
|
+
// React Native Web passes a DOM click, not a native responder event, to onPress.
|
|
5
|
+
const target = currentTarget as {
|
|
6
|
+
getBoundingClientRect(): { left: number; top: number; width: number; height: number };
|
|
7
|
+
};
|
|
8
|
+
const bounds = target.getBoundingClientRect();
|
|
9
|
+
return {
|
|
10
|
+
x:
|
|
11
|
+
typeof nativeEvent.clientX === 'number'
|
|
12
|
+
? nativeEvent.clientX - bounds.left
|
|
13
|
+
: bounds.width / 2,
|
|
14
|
+
y:
|
|
15
|
+
typeof nativeEvent.clientY === 'number'
|
|
16
|
+
? nativeEvent.clientY - bounds.top
|
|
17
|
+
: bounds.height / 2,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compose a chrome region's style. React Native Web renders object styles
|
|
5
|
+
* inline, and inline paint beats any class, so a class entry in the override
|
|
6
|
+
* (NativeWind's `$$css` marker) replaces the region's default paint entirely.
|
|
7
|
+
* Structure always applies; native style overrides simply merge last.
|
|
8
|
+
*/
|
|
9
|
+
export function regionStyle(
|
|
10
|
+
structure: ViewStyle,
|
|
11
|
+
paint: ViewStyle,
|
|
12
|
+
override: StyleProp<ViewStyle>,
|
|
13
|
+
): StyleProp<ViewStyle> {
|
|
14
|
+
return [structure, hasClassEntry(override) ? null : paint, override];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasClassEntry(style: unknown): boolean {
|
|
18
|
+
if (!style || typeof style !== 'object') return false;
|
|
19
|
+
if (Array.isArray(style)) return style.some((entry) => hasClassEntry(entry));
|
|
20
|
+
return '$$css' in style;
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Roster
|
|
2
|
+
|
|
3
|
+
This feature is about a roster; its children are lanes.
|
|
4
|
+
|
|
5
|
+
- Root entry exports: `Roster`, `useRoster`, the `RosterXxx` parts, and every
|
|
6
|
+
`XxxInput` zone type from `roster.types.ts`
|
|
7
|
+
|
|
8
|
+
- `index.tsx`: the chassis; calls `useRoster`, branches on `status`, composes zones
|
|
9
|
+
- `use-roster.ts`: the hook; owns window, projection, scroll, geometry, and press
|
|
10
|
+
- `layout.tsx`: arranges corner, header, label column, and body regions only
|
|
11
|
+
- `roster.types.ts`: props, model, and every zone input type
|
|
12
|
+
- `parts/`: collection-level parts (body, header, header cell, grid, corner, label column, empty)
|
|
13
|
+
- `lanes/`: `LaneRow`, the interval-hover platform pair, and lane-local parts
|
|
14
|
+
- `utils/`: ticks and the body content key
|
|
15
|
+
|
|
16
|
+
Interval and gap fillers live in `../layers`; press geometry and `regionStyle`
|
|
17
|
+
in `../primitives`. Zone contracts are documented in the README's Roster zones
|
|
18
|
+
section and in `llms.txt`. Tests: `test/components/roster`.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { RosterGap } from '../layers/parts/gap';
|
|
2
|
+
import { RosterInterval } from '../layers/parts/interval';
|
|
3
|
+
import { RosterLaneLabel } from './lanes/parts/lane-label';
|
|
4
|
+
import { RosterLayout } from './layout';
|
|
5
|
+
import { RosterBody } from './parts/body';
|
|
6
|
+
import { RosterCorner } from './parts/corner';
|
|
7
|
+
import { RosterEmpty } from './parts/empty';
|
|
8
|
+
import { RosterGrid } from './parts/grid';
|
|
9
|
+
import { RosterHeader } from './parts/header';
|
|
10
|
+
import { RosterHeaderCell } from './parts/header-cell';
|
|
11
|
+
import { RosterLaneLabelColumn } from './parts/lane-label-column';
|
|
12
|
+
import type { RosterProps } from './roster.types';
|
|
13
|
+
import { useRoster } from './use-roster';
|
|
14
|
+
|
|
15
|
+
export function Roster(props: RosterProps) {
|
|
16
|
+
const {
|
|
17
|
+
status,
|
|
18
|
+
orderedLanes,
|
|
19
|
+
laneState,
|
|
20
|
+
window,
|
|
21
|
+
geometryFor,
|
|
22
|
+
projection,
|
|
23
|
+
scroll,
|
|
24
|
+
press,
|
|
25
|
+
ticks,
|
|
26
|
+
contentWidth,
|
|
27
|
+
viewport,
|
|
28
|
+
onLayout,
|
|
29
|
+
} = useRoster(props);
|
|
30
|
+
const {
|
|
31
|
+
emptyZone = RosterEmpty,
|
|
32
|
+
cornerZone = RosterCorner,
|
|
33
|
+
headerZone = RosterHeader,
|
|
34
|
+
laneLabelColumnZone = RosterLaneLabelColumn,
|
|
35
|
+
bodyZone = RosterBody,
|
|
36
|
+
headerCellZone = RosterHeaderCell,
|
|
37
|
+
laneLabelZone = RosterLaneLabel,
|
|
38
|
+
intervalZone = RosterInterval,
|
|
39
|
+
gapZone = RosterGap,
|
|
40
|
+
gridZone = RosterGrid,
|
|
41
|
+
incompleteLabel = 'Availability may be incomplete',
|
|
42
|
+
neverSetLabel = 'No availability set',
|
|
43
|
+
} = props;
|
|
44
|
+
if (status === 'empty') return emptyZone();
|
|
45
|
+
if (window.start === window.end) return null;
|
|
46
|
+
return (
|
|
47
|
+
<RosterLayout
|
|
48
|
+
style={props.style}
|
|
49
|
+
headerStyle={props.headerStyle}
|
|
50
|
+
laneLabelColumnStyle={props.laneLabelColumnStyle}
|
|
51
|
+
bodyStyle={props.bodyStyle}
|
|
52
|
+
laneLabelWidth={props.laneLabelWidth}
|
|
53
|
+
onLayout={onLayout}
|
|
54
|
+
cornerZone={cornerZone()}
|
|
55
|
+
headerZone={headerZone({ ticks, projection, scroll, contentWidth, headerCellZone })}
|
|
56
|
+
laneLabelColumnZone={laneLabelColumnZone({
|
|
57
|
+
labels: orderedLanes.map((lane) => {
|
|
58
|
+
const state = laneState.get(lane.id) ?? { flag: 'none', complete: true };
|
|
59
|
+
return {
|
|
60
|
+
lane,
|
|
61
|
+
...state,
|
|
62
|
+
viewTimezone: projection.viewTimezone,
|
|
63
|
+
incompleteLabel,
|
|
64
|
+
neverSetLabel,
|
|
65
|
+
};
|
|
66
|
+
}),
|
|
67
|
+
projection,
|
|
68
|
+
scroll,
|
|
69
|
+
laneLabelZone,
|
|
70
|
+
})}
|
|
71
|
+
bodyZone={bodyZone({
|
|
72
|
+
lanes: orderedLanes,
|
|
73
|
+
window,
|
|
74
|
+
geometryFor,
|
|
75
|
+
projection,
|
|
76
|
+
scroll,
|
|
77
|
+
press,
|
|
78
|
+
ticks,
|
|
79
|
+
contentWidth,
|
|
80
|
+
viewport,
|
|
81
|
+
intervalZone,
|
|
82
|
+
gapZone,
|
|
83
|
+
gridZone,
|
|
84
|
+
highlightSource: props.highlightSource,
|
|
85
|
+
onIntervalHover: props.onIntervalHover,
|
|
86
|
+
incompleteLabel,
|
|
87
|
+
})}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Lane, LaneGeometry, Rect } from '../../../core';
|
|
2
|
+
import type { PressPointInput } from '../../primitives/press-point.types';
|
|
3
|
+
|
|
4
|
+
export type IntervalHoverInput = {
|
|
5
|
+
lane: Lane;
|
|
6
|
+
geometry: LaneGeometry;
|
|
7
|
+
onIntervalHover?: (rect: Rect, lane: Lane) => void;
|
|
8
|
+
};
|
|
9
|
+
export type IntervalHoverProps = {
|
|
10
|
+
onPointerMove?: (input: PressPointInput & { nativeEvent: { pointerType?: string } }) => void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { hitTest } from '../../../core/hit-test';
|
|
2
|
+
import { pressPoint } from '../../primitives/press-point.web';
|
|
3
|
+
import type { IntervalHoverInput, IntervalHoverProps } from './interval-hover.types';
|
|
4
|
+
|
|
5
|
+
export function intervalHoverProps({
|
|
6
|
+
lane,
|
|
7
|
+
geometry,
|
|
8
|
+
onIntervalHover,
|
|
9
|
+
}: IntervalHoverInput): IntervalHoverProps {
|
|
10
|
+
if (!onIntervalHover) return {};
|
|
11
|
+
return {
|
|
12
|
+
onPointerMove(input) {
|
|
13
|
+
if (input.nativeEvent.pointerType === 'touch') return;
|
|
14
|
+
const { x, y } = pressPoint(input);
|
|
15
|
+
const hit = hitTest(lane, geometry, x, y);
|
|
16
|
+
if (hit?.kind === 'interval') onIntervalHover(hit.rect, lane);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
import { Pressable } from 'react-native';
|
|
3
|
+
import type { Lane, LaneGeometry } from '../../../core';
|
|
4
|
+
import { pressPoint } from '../../primitives/press-point';
|
|
5
|
+
import type { BodyInput } from '../roster.types';
|
|
6
|
+
import { intervalHoverProps } from './interval-hover';
|
|
7
|
+
import { RosterIncomplete } from './parts/incomplete';
|
|
8
|
+
|
|
9
|
+
export type LaneRowProps = Pick<
|
|
10
|
+
BodyInput,
|
|
11
|
+
'press' | 'intervalZone' | 'gapZone' | 'highlightSource' | 'onIntervalHover' | 'incompleteLabel'
|
|
12
|
+
> & {
|
|
13
|
+
lane: Lane;
|
|
14
|
+
geometry: LaneGeometry;
|
|
15
|
+
width: number;
|
|
16
|
+
rowHeight: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function LaneRow({
|
|
20
|
+
lane,
|
|
21
|
+
geometry,
|
|
22
|
+
width,
|
|
23
|
+
rowHeight,
|
|
24
|
+
press,
|
|
25
|
+
intervalZone,
|
|
26
|
+
gapZone,
|
|
27
|
+
highlightSource,
|
|
28
|
+
onIntervalHover,
|
|
29
|
+
incompleteLabel = 'Availability may be incomplete',
|
|
30
|
+
}: LaneRowProps) {
|
|
31
|
+
const layers = [...lane.layers].sort((a, b) => a.z - b.z);
|
|
32
|
+
return (
|
|
33
|
+
<Pressable
|
|
34
|
+
{...intervalHoverProps({ lane, geometry, onIntervalHover })}
|
|
35
|
+
testID={`roster-lane-${lane.id}`}
|
|
36
|
+
accessibilityLabel={lane.label}
|
|
37
|
+
onPress={(input) => {
|
|
38
|
+
const point = pressPoint(input);
|
|
39
|
+
press(lane, point.x, point.y);
|
|
40
|
+
}}
|
|
41
|
+
style={{ width, height: rowHeight }}
|
|
42
|
+
>
|
|
43
|
+
<RosterIncomplete lane={lane} geometry={geometry} width={width} label={incompleteLabel} />
|
|
44
|
+
{layers.map((layer) => (
|
|
45
|
+
<Fragment key={layer.id}>
|
|
46
|
+
{geometry.rects
|
|
47
|
+
.filter((rect) => rect.layerId === layer.id)
|
|
48
|
+
.map((rect) => (
|
|
49
|
+
<Fragment key={`interval-${rect.x}:${rect.width}`}>
|
|
50
|
+
{intervalZone({
|
|
51
|
+
rect,
|
|
52
|
+
layer,
|
|
53
|
+
lane,
|
|
54
|
+
highlighted: rect.sources.some(
|
|
55
|
+
(source) =>
|
|
56
|
+
source.kind === highlightSource?.kind && source.id === highlightSource.id,
|
|
57
|
+
),
|
|
58
|
+
})}
|
|
59
|
+
</Fragment>
|
|
60
|
+
))}
|
|
61
|
+
{geometry.gapRects
|
|
62
|
+
.filter((rect) => rect.layerId === layer.id)
|
|
63
|
+
.map((rect) => (
|
|
64
|
+
<Pressable
|
|
65
|
+
key={`gap-${rect.x}:${rect.width}`}
|
|
66
|
+
accessibilityLabel={`${lane.label}: removed time`}
|
|
67
|
+
onPress={(input) => {
|
|
68
|
+
input.stopPropagation();
|
|
69
|
+
const point = pressPoint(input);
|
|
70
|
+
press(lane, rect.x + point.x, rect.y + point.y);
|
|
71
|
+
}}
|
|
72
|
+
style={{
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
left: rect.x,
|
|
75
|
+
top: rect.y,
|
|
76
|
+
width: rect.width,
|
|
77
|
+
height: rect.height,
|
|
78
|
+
zIndex: rect.z,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{gapZone({ rect, layer, lane })}
|
|
82
|
+
</Pressable>
|
|
83
|
+
))}
|
|
84
|
+
</Fragment>
|
|
85
|
+
))}
|
|
86
|
+
</Pressable>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Text } from 'react-native';
|
|
2
|
+
import type { Lane, LaneGeometry } from '../../../../core';
|
|
3
|
+
|
|
4
|
+
export function RosterIncomplete({
|
|
5
|
+
lane,
|
|
6
|
+
geometry,
|
|
7
|
+
width,
|
|
8
|
+
label,
|
|
9
|
+
}: {
|
|
10
|
+
lane: Lane;
|
|
11
|
+
geometry: LaneGeometry;
|
|
12
|
+
width: number;
|
|
13
|
+
label: string;
|
|
14
|
+
}) {
|
|
15
|
+
if (lane.complete !== false) return null;
|
|
16
|
+
// Choose the first empty span; the notice never covers an interval.
|
|
17
|
+
let left = 0;
|
|
18
|
+
let right = width;
|
|
19
|
+
for (const rect of [...geometry.rects].sort((a, b) => a.x - b.x)) {
|
|
20
|
+
if (rect.x > left) {
|
|
21
|
+
right = rect.x;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
left = Math.max(left, rect.x + rect.width);
|
|
25
|
+
}
|
|
26
|
+
if (left >= right) return null;
|
|
27
|
+
return (
|
|
28
|
+
<Text
|
|
29
|
+
testID={`roster-incomplete-${lane.id}`}
|
|
30
|
+
pointerEvents="none"
|
|
31
|
+
numberOfLines={1}
|
|
32
|
+
style={{
|
|
33
|
+
position: 'absolute',
|
|
34
|
+
left,
|
|
35
|
+
width: right - left,
|
|
36
|
+
top: 4,
|
|
37
|
+
color: '#64748b',
|
|
38
|
+
fontSize: 12,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{label}
|
|
42
|
+
</Text>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Text, View } from 'react-native';
|
|
2
|
+
import type { LaneLabelInput } from '../../roster.types';
|
|
3
|
+
|
|
4
|
+
export function RosterLaneLabel({
|
|
5
|
+
lane,
|
|
6
|
+
flag,
|
|
7
|
+
complete,
|
|
8
|
+
viewTimezone,
|
|
9
|
+
incompleteLabel,
|
|
10
|
+
neverSetLabel,
|
|
11
|
+
}: LaneLabelInput) {
|
|
12
|
+
const zone =
|
|
13
|
+
lane.timezone && lane.timezone !== viewTimezone ? (
|
|
14
|
+
<Text style={{ fontSize: 10, color: '#64748b' }}>{lane.timezone}</Text>
|
|
15
|
+
) : null;
|
|
16
|
+
const flagText =
|
|
17
|
+
flag === 'never-set' ? (
|
|
18
|
+
<Text style={{ fontSize: 10, color: '#9a3412' }}>{neverSetLabel}</Text>
|
|
19
|
+
) : null;
|
|
20
|
+
const completeness = complete ? null : (
|
|
21
|
+
<Text style={{ fontSize: 10, color: '#9a3412' }}>{incompleteLabel}</Text>
|
|
22
|
+
);
|
|
23
|
+
return (
|
|
24
|
+
<View
|
|
25
|
+
style={{
|
|
26
|
+
flex: 1,
|
|
27
|
+
justifyContent: 'center',
|
|
28
|
+
paddingHorizontal: 8,
|
|
29
|
+
borderBottomWidth: 1,
|
|
30
|
+
borderBottomColor: '#e2e8f0',
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<Text numberOfLines={1} style={{ color: '#0f172a', fontSize: 12 }}>
|
|
34
|
+
{lane.label}
|
|
35
|
+
</Text>
|
|
36
|
+
{zone}
|
|
37
|
+
{flagText}
|
|
38
|
+
{completeness}
|
|
39
|
+
</View>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { LayoutChangeEvent } from 'react-native';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { regionStyle } from '../primitives/region-style';
|
|
5
|
+
import type { RosterStyleProps } from './roster.types';
|
|
6
|
+
|
|
7
|
+
type RosterLayoutProps = Omit<RosterStyleProps, `${string}ClassName` | 'className'> & {
|
|
8
|
+
/** Cell above the lane labels, beside the header. */
|
|
9
|
+
cornerZone: ReactNode;
|
|
10
|
+
/** Frozen tick row; the layout clips translated header content. */
|
|
11
|
+
headerZone: ReactNode;
|
|
12
|
+
/** Frozen lane labels; the layout supplies the label column and clips vertical translation. */
|
|
13
|
+
laneLabelColumnZone: ReactNode;
|
|
14
|
+
/** Virtualized body with its horizontal scroller; fills the measured viewport. */
|
|
15
|
+
bodyZone: ReactNode;
|
|
16
|
+
onLayout: (input: LayoutChangeEvent) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function RosterLayout({
|
|
20
|
+
cornerZone,
|
|
21
|
+
headerZone,
|
|
22
|
+
laneLabelColumnZone,
|
|
23
|
+
bodyZone,
|
|
24
|
+
onLayout,
|
|
25
|
+
style,
|
|
26
|
+
headerStyle,
|
|
27
|
+
laneLabelColumnStyle,
|
|
28
|
+
bodyStyle,
|
|
29
|
+
laneLabelWidth = 180,
|
|
30
|
+
}: RosterLayoutProps) {
|
|
31
|
+
return (
|
|
32
|
+
<View style={regionStyle(structure.root, paint.root, style)}>
|
|
33
|
+
<View style={regionStyle(structure.header, paint.header, headerStyle)}>
|
|
34
|
+
<View style={{ width: laneLabelWidth }}>{cornerZone}</View>
|
|
35
|
+
<View style={structure.headerStrip}>{headerZone}</View>
|
|
36
|
+
</View>
|
|
37
|
+
<View style={structure.content}>
|
|
38
|
+
<View
|
|
39
|
+
style={regionStyle(
|
|
40
|
+
{ ...structure.labels, width: laneLabelWidth },
|
|
41
|
+
paint.labels,
|
|
42
|
+
laneLabelColumnStyle,
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{laneLabelColumnZone}
|
|
46
|
+
</View>
|
|
47
|
+
<View onLayout={onLayout} style={regionStyle(structure.body, {}, bodyStyle)}>
|
|
48
|
+
{bodyZone}
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
</View>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const structure = {
|
|
56
|
+
root: { flex: 1, minHeight: 0, overflow: 'hidden' },
|
|
57
|
+
header: { flexDirection: 'row', height: 40, overflow: 'hidden' },
|
|
58
|
+
headerStrip: { flex: 1, minWidth: 0, overflow: 'hidden' },
|
|
59
|
+
content: { flex: 1, minHeight: 0, flexDirection: 'row' },
|
|
60
|
+
labels: { overflow: 'hidden' },
|
|
61
|
+
body: { flex: 1, minWidth: 0, overflow: 'hidden' },
|
|
62
|
+
} as const;
|
|
63
|
+
// Paint is what a class replaces; see regionStyle.
|
|
64
|
+
const paint = {
|
|
65
|
+
root: { backgroundColor: '#fff' },
|
|
66
|
+
header: { borderBottomWidth: 1, borderBottomColor: '#cbd5e1' },
|
|
67
|
+
labels: { borderRightWidth: 1, borderRightColor: '#cbd5e1' },
|
|
68
|
+
} as const;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { LegendList } from '@legendapp/list';
|
|
2
|
+
import { Profiler, type ProfilerOnRenderCallback } from 'react';
|
|
3
|
+
import { ScrollView, View } from 'react-native';
|
|
4
|
+
import { LaneRow } from '../lanes/lane';
|
|
5
|
+
import type { BodyInput } from '../roster.types';
|
|
6
|
+
import { bodyContentKey } from '../utils/body-content-key';
|
|
7
|
+
|
|
8
|
+
export function RosterBody({
|
|
9
|
+
lanes,
|
|
10
|
+
geometryFor,
|
|
11
|
+
projection,
|
|
12
|
+
scroll,
|
|
13
|
+
press,
|
|
14
|
+
ticks,
|
|
15
|
+
contentWidth,
|
|
16
|
+
viewport,
|
|
17
|
+
window,
|
|
18
|
+
intervalZone,
|
|
19
|
+
gapZone,
|
|
20
|
+
gridZone,
|
|
21
|
+
highlightSource,
|
|
22
|
+
onIntervalHover,
|
|
23
|
+
incompleteLabel,
|
|
24
|
+
onRowRender,
|
|
25
|
+
}: BodyInput & {
|
|
26
|
+
/** Optional per-lane commit observer; used by the development gallery's Profiler gate. */
|
|
27
|
+
onRowRender?: ProfilerOnRenderCallback;
|
|
28
|
+
}) {
|
|
29
|
+
// LegendList requires a measured viewport and does not support static rendering.
|
|
30
|
+
if (viewport.width <= 0 || viewport.height <= 0) return null;
|
|
31
|
+
return (
|
|
32
|
+
<ScrollView
|
|
33
|
+
testID="roster-horizontal-scroll"
|
|
34
|
+
ref={scroll.bodyRef}
|
|
35
|
+
horizontal
|
|
36
|
+
onScroll={scroll.onBodyScroll}
|
|
37
|
+
scrollEventThrottle={16}
|
|
38
|
+
style={{ flex: 1 }}
|
|
39
|
+
>
|
|
40
|
+
<View style={{ width: contentWidth, height: viewport.height }}>
|
|
41
|
+
{gridZone({ ticks, contentWidth })}
|
|
42
|
+
<LegendList
|
|
43
|
+
testID="roster-vertical-scroll"
|
|
44
|
+
data={lanes}
|
|
45
|
+
extraData={bodyContentKey({
|
|
46
|
+
window,
|
|
47
|
+
projection,
|
|
48
|
+
highlightSource,
|
|
49
|
+
intervalZone,
|
|
50
|
+
gapZone,
|
|
51
|
+
onIntervalHover,
|
|
52
|
+
incompleteLabel,
|
|
53
|
+
})}
|
|
54
|
+
keyExtractor={(lane) => lane.id}
|
|
55
|
+
estimatedListSize={{ width: contentWidth, height: viewport.height }}
|
|
56
|
+
estimatedItemSize={projection.rowHeight}
|
|
57
|
+
getFixedItemSize={() => projection.rowHeight}
|
|
58
|
+
drawDistance={0}
|
|
59
|
+
recycleItems={false}
|
|
60
|
+
maintainVisibleContentPosition={false}
|
|
61
|
+
onScroll={scroll.onVerticalScroll}
|
|
62
|
+
scrollEventThrottle={16}
|
|
63
|
+
style={{ flex: 1 }}
|
|
64
|
+
renderItem={({ item }) => {
|
|
65
|
+
const row = (
|
|
66
|
+
<LaneRow
|
|
67
|
+
lane={item}
|
|
68
|
+
geometry={geometryFor(item)}
|
|
69
|
+
width={contentWidth}
|
|
70
|
+
rowHeight={projection.rowHeight}
|
|
71
|
+
press={press}
|
|
72
|
+
intervalZone={intervalZone}
|
|
73
|
+
gapZone={gapZone}
|
|
74
|
+
highlightSource={highlightSource}
|
|
75
|
+
onIntervalHover={onIntervalHover}
|
|
76
|
+
incompleteLabel={incompleteLabel}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
if (onRowRender)
|
|
80
|
+
return (
|
|
81
|
+
<Profiler id={item.id} onRender={onRowRender}>
|
|
82
|
+
{row}
|
|
83
|
+
</Profiler>
|
|
84
|
+
);
|
|
85
|
+
return row;
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
</View>
|
|
89
|
+
</ScrollView>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import type { GridInput } from '../roster.types';
|
|
3
|
+
|
|
4
|
+
export function RosterGrid({ ticks, contentWidth }: GridInput) {
|
|
5
|
+
return (
|
|
6
|
+
<View
|
|
7
|
+
testID="roster-grid"
|
|
8
|
+
pointerEvents="none"
|
|
9
|
+
style={{ position: 'absolute', width: contentWidth, height: '100%' }}
|
|
10
|
+
>
|
|
11
|
+
{ticks.map((tick) => (
|
|
12
|
+
<View
|
|
13
|
+
key={tick.time}
|
|
14
|
+
style={{
|
|
15
|
+
position: 'absolute',
|
|
16
|
+
left: tick.x,
|
|
17
|
+
top: 0,
|
|
18
|
+
bottom: 0,
|
|
19
|
+
width: 1,
|
|
20
|
+
backgroundColor: '#e2e8f0',
|
|
21
|
+
}}
|
|
22
|
+
/>
|
|
23
|
+
))}
|
|
24
|
+
</View>
|
|
25
|
+
);
|
|
26
|
+
}
|