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.
This commit is contained in:
@@ -319,6 +319,7 @@ interface Props {
|
|||||||
modelValue: boolean;
|
modelValue: boolean;
|
||||||
feature: FeatureResponse | null;
|
feature: FeatureResponse | null;
|
||||||
featureState: FeatureStateResponse | null;
|
featureState: FeatureStateResponse | null;
|
||||||
|
initialTab?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
@@ -472,7 +473,7 @@ watch(
|
|||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(open) => {
|
(open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
activeTab.value = 'value';
|
activeTab.value = props.initialTab ?? 'value';
|
||||||
valueErrorMessage.value = null;
|
valueErrorMessage.value = null;
|
||||||
settingsErrorMessage.value = null;
|
settingsErrorMessage.value = null;
|
||||||
segmentsErrorMessage.value = null;
|
segmentsErrorMessage.value = null;
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div data-testid="feature-value-editor">
|
<div data-testid="feature-value-editor">
|
||||||
<!-- Type selector -->
|
<!-- Type selector -->
|
||||||
<v-btn-toggle
|
<div class="d-flex mb-3" data-testid="value-mode-toggle">
|
||||||
v-model="detectedMode"
|
<v-btn
|
||||||
density="compact"
|
size="small"
|
||||||
data-testid="value-mode-toggle"
|
:variant="detectedMode === 'text' ? 'flat' : 'outlined'"
|
||||||
>
|
color="primary"
|
||||||
<v-btn value="text" size="small">Text</v-btn>
|
rounded="0"
|
||||||
<v-btn value="json" size="small">JSON</v-btn>
|
style="border-radius: 4px 0 0 4px"
|
||||||
</v-btn-toggle>
|
@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 -->
|
<!-- JSON mode -->
|
||||||
<v-textarea
|
<v-textarea
|
||||||
@@ -83,11 +93,3 @@ const rules = {
|
|||||||
};
|
};
|
||||||
</script>
|
</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>
|
|
||||||
|
|||||||
@@ -3,18 +3,26 @@
|
|||||||
<!-- AND / OR toggle -->
|
<!-- AND / OR toggle -->
|
||||||
<div class="d-flex align-center ga-2 mb-3">
|
<div class="d-flex align-center ga-2 mb-3">
|
||||||
<span class="text-body-2 font-weight-medium text-medium-emphasis">Match</span>
|
<span class="text-body-2 font-weight-medium text-medium-emphasis">Match</span>
|
||||||
<v-btn-toggle
|
<div class="d-flex" data-testid="rule-type-toggle">
|
||||||
:model-value="rule.type"
|
<v-btn
|
||||||
density="compact"
|
size="small"
|
||||||
mandatory
|
:variant="rule.type === 'All' ? 'flat' : 'outlined'"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="outlined"
|
rounded="0"
|
||||||
data-testid="rule-type-toggle"
|
style="border-radius: 4px 0 0 4px"
|
||||||
@update:model-value="onTypeChange"
|
data-testid="rule-type-and"
|
||||||
>
|
@click="onTypeChange('All')"
|
||||||
<v-btn value="All" size="small" data-testid="rule-type-and">ALL (AND)</v-btn>
|
>ALL (AND)</v-btn>
|
||||||
<v-btn value="Any" size="small" data-testid="rule-type-or">ANY (OR)</v-btn>
|
<v-btn
|
||||||
</v-btn-toggle>
|
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-spacer />
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="removable"
|
v-if="removable"
|
||||||
|
|||||||
@@ -53,7 +53,26 @@
|
|||||||
<!-- Name + description -->
|
<!-- Name + description -->
|
||||||
<template #item.name="{ item }: { item: FeatureResponse }">
|
<template #item.name="{ item }: { item: FeatureResponse }">
|
||||||
<div>
|
<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">
|
<p v-if="item.description" class="text-caption text-medium-emphasis mb-0">
|
||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
</p>
|
</p>
|
||||||
@@ -158,6 +177,7 @@
|
|||||||
v-model="showDetail"
|
v-model="showDetail"
|
||||||
:feature="selectedFeature"
|
:feature="selectedFeature"
|
||||||
:feature-state="selectedFeatureState"
|
:feature-state="selectedFeatureState"
|
||||||
|
:initial-tab="detailInitialTab"
|
||||||
@updated="onFeatureUpdated"
|
@updated="onFeatureUpdated"
|
||||||
@deleted="onFeatureDeleted"
|
@deleted="onFeatureDeleted"
|
||||||
@state-updated="onStateUpdated"
|
@state-updated="onStateUpdated"
|
||||||
@@ -236,6 +256,7 @@ function showError(message: string): void {
|
|||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
const showDetail = ref(false);
|
const showDetail = ref(false);
|
||||||
const selectedFeature = ref<FeatureResponse | null>(null);
|
const selectedFeature = ref<FeatureResponse | null>(null);
|
||||||
|
const detailInitialTab = ref('value');
|
||||||
|
|
||||||
// Delete dialog state
|
// Delete dialog state
|
||||||
const showDeleteDialog = ref(false);
|
const showDeleteDialog = ref(false);
|
||||||
@@ -333,6 +354,13 @@ function openCreateDialog(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onRowClick(_event: Event, { item }: { item: FeatureResponse }): 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;
|
selectedFeature.value = item;
|
||||||
showDetail.value = true;
|
showDetail.value = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user