refactor(timetable): extract computeStageRowHeight helper; StageHeaderCell takes :row-height-px prop (B2)
Pure structural seam — no layout changes yet (B3 wires the page through). apps/app/src/lib/timetable/row-height.ts (NEW): computeStageRowHeight(laneCount, laneHeightPx, lanePadPx) — one-line pure function with the existing math: max(1, laneCount) * (laneHeight + lanePad) + lanePad. Math.max(1, laneCount) keeps an empty stage row visible at single-lane height instead of collapsing. apps/app/src/components/timetable/StageRow.vue: Switches its inline rowHeightPx computation to call the helper. Behavior identical (the math was the helper's body). apps/app/src/components/timetable/StageHeaderCell.vue: New optional `rowHeightPx?: number` prop. When provided (B3 will pass it from the page via the same helper), the header root applies blockSize inline so the sticky-left column aligns pixel-for-pixel with the row. When omitted, the legacy `block-size: 100%` CSS still applies — every existing call-site keeps working. apps/app/src/lib/timetable/index.ts: re-export the new helper. Tests still green (389 across 54 files); typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { Stage } from '@/types/timetable'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
stage: Stage
|
||||
/** Number of conflicts on this stage row for the active day. */
|
||||
conflictCount?: number
|
||||
/**
|
||||
* Explicit row height in pixels. When provided (page passes it from
|
||||
* computeStageRowHeight), the header sizes to match the corresponding
|
||||
* StageRow exactly so the sticky-left column aligns pixel-for-pixel.
|
||||
* When omitted, the legacy `block-size: 100%` CSS path takes over.
|
||||
*/
|
||||
rowHeightPx?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
edit: [stage: Stage]
|
||||
delete: [stage: Stage]
|
||||
}>()
|
||||
|
||||
const heightStyle = computed(() =>
|
||||
typeof props.rowHeightPx === 'number'
|
||||
? { blockSize: `${props.rowHeightPx}px` }
|
||||
: null,
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="tt-stage-header"
|
||||
:data-stage-id="stage.id"
|
||||
:style="heightStyle"
|
||||
>
|
||||
<span
|
||||
class="tt-stage-header__swatch"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import PerformanceBlock from './PerformanceBlock.vue'
|
||||
import { computeStageRowHeight } from '@/lib/timetable/row-height'
|
||||
import { isoToMinutes, minutesToPx } from '@/lib/timetable/time-grid'
|
||||
import type { Performance, Stage } from '@/types/timetable'
|
||||
|
||||
@@ -30,7 +31,7 @@ const emit = defineEmits<{
|
||||
const laneHeightPx = 44
|
||||
const lanePadPx = 4
|
||||
const totalWidthPx = computed(() => props.totalMinutes * props.pxPerMin)
|
||||
const rowHeightPx = computed(() => Math.max(1, props.laneCount) * (laneHeightPx + lanePadPx) + lanePadPx)
|
||||
const rowHeightPx = computed(() => computeStageRowHeight(props.laneCount, laneHeightPx, lanePadPx))
|
||||
|
||||
interface PositionedBlock {
|
||||
perf: Performance
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './conflict'
|
||||
export * from './b2b'
|
||||
export * from './capacity'
|
||||
export * from './lane'
|
||||
export * from './row-height'
|
||||
|
||||
18
apps/app/src/lib/timetable/row-height.ts
Normal file
18
apps/app/src/lib/timetable/row-height.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Per-stage row height in pixels for the timetable canvas.
|
||||
*
|
||||
* The `Math.max(1, laneCount)` keeps an empty stage row visible at the
|
||||
* single-lane height instead of collapsing to zero. Lanes are stacked
|
||||
* with a small gap between them, plus an outer pad above and below.
|
||||
*
|
||||
* Used by both StageRow (to size its own positioned-absolute lane area)
|
||||
* and StageHeaderCell (so the sticky-left header column aligns with the
|
||||
* scrolling row content pixel-for-pixel).
|
||||
*/
|
||||
export function computeStageRowHeight(
|
||||
laneCount: number,
|
||||
laneHeightPx: number,
|
||||
lanePadPx: number,
|
||||
): number {
|
||||
return Math.max(1, laneCount) * (laneHeightPx + lanePadPx) + lanePadPx
|
||||
}
|
||||
Reference in New Issue
Block a user