({
sessionCounter,
sessionCounterText,
isQuotaExhausted,
exhaustedMessageText,
maxHeight,
})
| 41 | * stay visually grouped. |
| 42 | */ |
| 43 | export const LimitedLandingPanel: React.FC<LimitedLandingPanelProps> = ({ |
| 44 | sessionCounter, |
| 45 | sessionCounterText, |
| 46 | isQuotaExhausted, |
| 47 | exhaustedMessageText, |
| 48 | maxHeight, |
| 49 | }) => { |
| 50 | const theme = useTheme() |
| 51 | const { contentMaxWidth } = useTerminalDimensions() |
| 52 | const model = getFreebuffModel(LIMITED_FREEBUFF_MODEL_ID) |
| 53 | const [pending, setPending] = useState(false) |
| 54 | const scrollRef = useRef<ScrollBoxRenderable | null>(null) |
| 55 | |
| 56 | // Rendered height of the panel, matching the JSX below row-for-row so the |
| 57 | // scroll budget is exact: name + warning (each wrap-aware) + the counter |
| 58 | // line with its 1-row top/bottom margins + either the 3-row bordered button |
| 59 | // or the exhausted-quota message. |
| 60 | const exhaustedTitleText = 'Daily session limit reached' |
| 61 | const wrappedRows = (text: string) => |
| 62 | Math.max(1, Math.ceil(text.length / contentMaxWidth)) |
| 63 | const BUTTON_ROWS = 3 // 2 border rows + label |
| 64 | const actionRows = isQuotaExhausted |
| 65 | ? wrappedRows(exhaustedTitleText) + wrappedRows(exhaustedMessageText) |
| 66 | : BUTTON_ROWS |
| 67 | const contentHeight = |
| 68 | wrappedRows(model.displayName) + |
| 69 | (model.warning ? wrappedRows(model.warning) : 0) + |
| 70 | 1 /* counter marginTop */ + |
| 71 | wrappedRows(sessionCounterText) + |
| 72 | 1 /* counter marginBottom */ + |
| 73 | actionRows |
| 74 | const needsScroll = contentHeight > maxHeight |
| 75 | const viewportHeight = Math.max(1, Math.min(contentHeight, maxHeight)) |
| 76 | |
| 77 | // A scrollbox stretches to fill its parent, which would left-align the |
| 78 | // panel; the old plain box sized to its content and the parent centered |
| 79 | // it. Restore that by pinning the scrollbox to its content width (widest |
| 80 | // of name / warning / counter / the bordered button) so `alignItems: |
| 81 | // 'center'` on the parent can center the whole block again. |
| 82 | const BUTTON_LABEL = 'Start session Enter' |
| 83 | const BUTTON_CHROME = 6 // 2 border + 4 padding (paddingLeft/Right 2) |
| 84 | const actionWidth = isQuotaExhausted |
| 85 | ? Math.max(exhaustedTitleText.length, exhaustedMessageText.length) |
| 86 | : BUTTON_LABEL.length + BUTTON_CHROME |
| 87 | const panelWidth = |
| 88 | Math.min( |
| 89 | contentMaxWidth, |
| 90 | Math.max( |
| 91 | model.displayName.length, |
| 92 | model.warning?.length ?? 0, |
| 93 | sessionCounterText.length, |
| 94 | actionWidth, |
| 95 | ), |
| 96 | ) + (needsScroll ? 1 : 0) /* scrollbar gutter */ |
| 97 | |
| 98 | const interactable = !pending && !isQuotaExhausted |
| 99 | |
| 100 | const start = useCallback(() => { |
nothing calls this directly
no test coverage detected