Fix v-btn-toggle overlap in dialogs; add segment override icon to features grid

Replace v-btn-toggle with manual button pairs in RuleGroupEditor and
FeatureValueEditor — VSlideGroup (used internally) measures zero width
inside teleported dialogs, causing buttons to overlap and show scroll arrows.

Add donut chart icon next to feature names in the features grid when segment
overrides exist; clicking opens FeatureDetail directly on the Segments tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 10:25:50 -07:00
parent d555e3b2d2
commit cbf1aba219
4 changed files with 69 additions and 30 deletions

View File

@@ -319,6 +319,7 @@ interface Props {
modelValue: boolean;
feature: FeatureResponse | null;
featureState: FeatureStateResponse | null;
initialTab?: string;
}
const props = defineProps<Props>();
@@ -472,7 +473,7 @@ watch(
() => props.modelValue,
(open) => {
if (open) {
activeTab.value = 'value';
activeTab.value = props.initialTab ?? 'value';
valueErrorMessage.value = null;
settingsErrorMessage.value = null;
segmentsErrorMessage.value = null;

View File

@@ -1,14 +1,24 @@
<template>
<div data-testid="feature-value-editor">
<!-- Type selector -->
<v-btn-toggle
v-model="detectedMode"
density="compact"
data-testid="value-mode-toggle"
>
<v-btn value="text" size="small">Text</v-btn>
<v-btn value="json" size="small">JSON</v-btn>
</v-btn-toggle>
<div class="d-flex mb-3" data-testid="value-mode-toggle">
<v-btn
size="small"
:variant="detectedMode === 'text' ? 'flat' : 'outlined'"
color="primary"
rounded="0"
style="border-radius: 4px 0 0 4px"
@click="detectedMode = 'text'"
>Text</v-btn>
<v-btn
size="small"
:variant="detectedMode === 'json' ? 'flat' : 'outlined'"
color="primary"
rounded="0"
style="border-radius: 0 4px 4px 0; margin-left: -1px"
@click="detectedMode = 'json'"
>JSON</v-btn>
</div>
<!-- JSON mode -->
<v-textarea
@@ -83,11 +93,3 @@ const rules = {
};
</script>
<style scoped>
/* Materio forces square icon-toggle sizing; override for text-label toggles */
:deep(.v-btn-toggle .v-btn) {
block-size: auto !important;
inline-size: auto !important;
padding-inline: 16px !important;
}
</style>

View File

@@ -3,18 +3,26 @@
<!-- AND / OR toggle -->
<div class="d-flex align-center ga-2 mb-3">
<span class="text-body-2 font-weight-medium text-medium-emphasis">Match</span>
<v-btn-toggle
:model-value="rule.type"
density="compact"
mandatory
color="primary"
variant="outlined"
data-testid="rule-type-toggle"
@update:model-value="onTypeChange"
>
<v-btn value="All" size="small" data-testid="rule-type-and">ALL (AND)</v-btn>
<v-btn value="Any" size="small" data-testid="rule-type-or">ANY (OR)</v-btn>
</v-btn-toggle>
<div class="d-flex" data-testid="rule-type-toggle">
<v-btn
size="small"
:variant="rule.type === 'All' ? 'flat' : 'outlined'"
color="primary"
rounded="0"
style="border-radius: 4px 0 0 4px"
data-testid="rule-type-and"
@click="onTypeChange('All')"
>ALL (AND)</v-btn>
<v-btn
size="small"
:variant="rule.type === 'Any' ? 'flat' : 'outlined'"
color="primary"
rounded="0"
style="border-radius: 0 4px 4px 0; margin-left: -1px"
data-testid="rule-type-or"
@click="onTypeChange('Any')"
>ANY (OR)</v-btn>
</div>
<v-spacer />
<v-btn
v-if="removable"

View File

@@ -53,7 +53,26 @@
<!-- Name + description -->
<template #item.name="{ item }: { item: FeatureResponse }">
<div>
<span class="text-body-2 font-weight-medium">{{ item.name }}</span>
<div class="d-flex align-center ga-1">
<span class="text-subtitle-1 font-weight-bold">{{ item.name }}</span>
<v-tooltip
v-if="(featureSegmentsMap.get(item.id) ?? []).length > 0"
text="Has segment overrides"
location="top"
>
<template #activator="{ props: tooltipProps }">
<v-btn
v-bind="tooltipProps"
icon="ri-donut-chart-fill"
size="small"
variant="text"
color="secondary"
:data-testid="`segment-override-icon-${item.id}`"
@click.stop="openDetailOnSegments(item)"
/>
</template>
</v-tooltip>
</div>
<p v-if="item.description" class="text-caption text-medium-emphasis mb-0">
{{ item.description }}
</p>
@@ -158,6 +177,7 @@
v-model="showDetail"
:feature="selectedFeature"
:feature-state="selectedFeatureState"
:initial-tab="detailInitialTab"
@updated="onFeatureUpdated"
@deleted="onFeatureDeleted"
@state-updated="onStateUpdated"
@@ -236,6 +256,7 @@ function showError(message: string): void {
const showDialog = ref(false);
const showDetail = ref(false);
const selectedFeature = ref<FeatureResponse | null>(null);
const detailInitialTab = ref('value');
// Delete dialog state
const showDeleteDialog = ref(false);
@@ -333,6 +354,13 @@ function openCreateDialog(): void {
}
function onRowClick(_event: Event, { item }: { item: FeatureResponse }): void {
detailInitialTab.value = 'value';
selectedFeature.value = item;
showDetail.value = true;
}
function openDetailOnSegments(item: FeatureResponse): void {
detailInitialTab.value = 'segments';
selectedFeature.value = item;
showDetail.value = true;
}