fix(app): lay out the tab modified dot inline
The dot was absolutely positioned at the chip's right edge, so it painted on top of a truncated label. Render it as the last flex child of the tab content row and widen a modified tab by the dot plus its gap so the label keeps the room it had.
This commit is contained in:
@@ -122,6 +122,7 @@ const TAB_ROW_PADDING_HORIZONTAL = 4;
|
||||
const TAB_ICON_WIDTH = 14;
|
||||
const TAB_CONTENT_GAP = 4;
|
||||
const TAB_DROP_INDICATOR_WIDTH = 4;
|
||||
const TAB_MODIFIED_DOT_SIZE = 8;
|
||||
const TAB_MIN_WIDTH = 96;
|
||||
const TAB_MAX_WIDTH = 160;
|
||||
const TAB_CLOSE_BUTTON_RESERVED_WIDTH = 0;
|
||||
@@ -607,6 +608,7 @@ interface ResolvedWorkspaceDesktopTabRowItem extends WorkspaceDesktopTabRowItem
|
||||
interface WorkspaceTabLabel {
|
||||
key: string;
|
||||
label: string;
|
||||
modified: boolean;
|
||||
}
|
||||
|
||||
interface WorkspaceTabLabelMeasurement {
|
||||
@@ -630,12 +632,15 @@ function completeWorkspaceTabLabelWidths(
|
||||
measurements: Map<string, WorkspaceTabLabelMeasurement>,
|
||||
): number[] | null {
|
||||
const widths: number[] = [];
|
||||
for (const { key, label } of labels) {
|
||||
for (const { key, label, modified } of labels) {
|
||||
const measurement = measurements.get(key);
|
||||
if (!measurement || measurement.label !== label || measurement.width <= 0) {
|
||||
return null;
|
||||
}
|
||||
widths.push(measurement.width + TAB_LABEL_LAYOUT_ALLOWANCE);
|
||||
// The modified dot sits in the content row, so a modified tab needs that much more width
|
||||
// before its label starts truncating.
|
||||
const modifiedAllowance = modified ? TAB_CONTENT_GAP + TAB_MODIFIED_DOT_SIZE : 0;
|
||||
widths.push(measurement.width + TAB_LABEL_LAYOUT_ALLOWANCE + modifiedAllowance);
|
||||
}
|
||||
return widths;
|
||||
}
|
||||
@@ -803,6 +808,7 @@ function TabHandleContent({
|
||||
backdrop,
|
||||
tabLabelSkeletonStyle,
|
||||
tabLabelStyle,
|
||||
modifiedTestId,
|
||||
}: {
|
||||
presentation: WorkspaceTabPresentation;
|
||||
isHighlighted: boolean;
|
||||
@@ -810,7 +816,9 @@ function TabHandleContent({
|
||||
backdrop: SurfaceBackdrop;
|
||||
tabLabelSkeletonStyle: React.ComponentProps<typeof View>["style"];
|
||||
tabLabelStyle: React.ComponentProps<typeof Text>["style"];
|
||||
modifiedTestId: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const tabHandleDataSet = useMemo(
|
||||
() => ({ statusBucket: presentation.statusBucket ?? "none" }),
|
||||
[presentation.statusBucket],
|
||||
@@ -829,6 +837,15 @@ function TabHandleContent({
|
||||
{presentation.label}
|
||||
</Text>
|
||||
) : null}
|
||||
{/* The dot is a laid-out sibling of the label, not an overlay, so a truncated label ends
|
||||
before it instead of running underneath it. */}
|
||||
{presentation.modified ? (
|
||||
<View
|
||||
style={styles.tabModifiedDot}
|
||||
accessibilityLabel={t("workspace.tabs.modified")}
|
||||
testID={modifiedTestId}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -868,7 +885,6 @@ function TabChip({
|
||||
onCloseTab: (tabId: string) => Promise<void> | void;
|
||||
dragHandleProps: DraggableListDragHandleProps | undefined;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab;
|
||||
const middleClickRef = useMiddleClickClose(
|
||||
useCallback(() => void onCloseTab(tab.tabId), [onCloseTab, tab.tabId]),
|
||||
@@ -982,16 +998,8 @@ function TabChip({
|
||||
backdrop={chipBackdrop}
|
||||
tabLabelSkeletonStyle={tabLabelSkeletonStyle}
|
||||
tabLabelStyle={tabLabelStyle}
|
||||
modifiedTestId={`workspace-tab-modified-${buildDeterministicWorkspaceTabId(tab.target)}`}
|
||||
/>
|
||||
{presentation.modified ? (
|
||||
<View
|
||||
style={styles.tabModifiedIndicator}
|
||||
accessibilityLabel={t("workspace.tabs.modified")}
|
||||
testID={`workspace-tab-modified-${buildDeterministicWorkspaceTabId(tab.target)}`}
|
||||
>
|
||||
<View style={styles.tabModifiedDot} />
|
||||
</View>
|
||||
) : null}
|
||||
</ContextMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
@@ -1305,7 +1313,7 @@ function ResolvedWorkspaceDesktopTabsRow({
|
||||
tab.presentation.titleState === "loading"
|
||||
? getFallbackTabLabel(tab.tab, fallbackTabLabels)
|
||||
: tab.presentation.label;
|
||||
return { key: tab.tab.key, label };
|
||||
return { key: tab.tab.key, label, modified: tab.presentation.modified };
|
||||
}),
|
||||
[fallbackTabLabels, tabs],
|
||||
);
|
||||
@@ -2009,17 +2017,10 @@ const styles = StyleSheet.create((theme) => ({
|
||||
justifyContent: "center",
|
||||
zIndex: 1,
|
||||
},
|
||||
tabModifiedIndicator: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
tabModifiedDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
width: TAB_MODIFIED_DOT_SIZE,
|
||||
height: TAB_MODIFIED_DOT_SIZE,
|
||||
flexShrink: 0,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
backgroundColor: theme.colors.foregroundMuted,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user