Drives in chassis (max {{ maxQty }})
@@ -195,33 +237,59 @@ function pickDrive(value: string): void {
font-weight: 600;
}
-.drive-bay-group__list {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
+.drive-bay-group__select {
+ :deep(.v-field) {
+ border-radius: 12px;
+ background: rgba(var(--v-theme-on-surface), 0.03);
+ }
-.drive-bay-group__option {
- display: flex;
- align-items: flex-start;
- text-align: left;
- width: 100%;
- padding: 12px 16px;
- border-radius: 12px;
- border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
- background: rgba(var(--v-theme-on-surface), 0.03);
- cursor: pointer;
- transition: all 0.15s ease;
+ :deep(.v-field--variant-outlined) {
+ --v-field-border-opacity: 0.16;
+ }
- &:hover {
- border-color: rgba(var(--v-theme-primary), 0.45);
- background: rgba(var(--v-theme-primary), 0.06);
+ :deep(.v-field--focused .v-field__outline) {
+ --v-field-border-opacity: 1;
}
}
-.drive-bay-group__option--active {
- border-color: rgb(var(--v-theme-primary));
- background: rgba(var(--v-theme-primary), 0.1);
+.drive-bay-group__row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ gap: 12px;
+}
+
+.drive-bay-group__row-label {
+ font-weight: 600;
+ font-size: 14px;
+ color: rgba(var(--v-theme-on-surface), 0.95);
+ flex: 1 1 auto;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.drive-bay-group__row--menu .drive-bay-group__row-label {
+ white-space: normal;
+}
+
+.drive-bay-group__row-price {
+ flex-shrink: 0;
+ font-variant-numeric: tabular-nums;
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.drive-bay-group__row-price--zero {
+ color: rgba(var(--v-theme-on-surface), 0.5);
+ font-weight: 500;
+ font-size: 12px;
+}
+
+.drive-bay-group__row-price--paid {
+ color: rgb(var(--v-theme-primary));
}
.drive-bay-group__qty {
@@ -272,3 +340,9 @@ function pickDrive(value: string): void {
font-variant-numeric: tabular-nums;
}
+
+
diff --git a/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/OptionGroupSelector.vue b/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/OptionGroupSelector.vue
index c222c6c..96d7dd3 100644
--- a/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/OptionGroupSelector.vue
+++ b/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/OptionGroupSelector.vue
@@ -14,7 +14,12 @@ const emit = defineEmits<{
'update:selected': [value: string]
}>()
-const values = computed
(() => props.group.options[0]?.values ?? [])
+interface SelectItem {
+ value: string
+ label: string
+ price: number
+ isDefault: boolean
+}
function priceFor(v: DedicatedConfigValue): number {
const raw = props.cycle === 'monthly'
@@ -27,11 +32,34 @@ function priceFor(v: DedicatedConfigValue): number {
return raw ? parseFloat(raw) : 0
}
-function priceLabel(v: DedicatedConfigValue): string {
- const p = priceFor(v)
- if (p === 0) return v.is_default ? 'included' : 'no extra cost'
- const suffix = props.cycle === 'monthly' ? '/mo' : props.cycle === 'quarterly' ? '/qtr' : props.cycle === 'semi_annual' ? '/6mo' : '/yr'
- return `+$${p.toFixed(2)}${suffix}`
+const items = computed(() =>
+ (props.group.options[0]?.values ?? []).map(v => ({
+ value: v.value,
+ label: v.label,
+ price: priceFor(v),
+ isDefault: v.is_default,
+ })),
+)
+
+const cycleSuffix = computed(() =>
+ props.cycle === 'monthly'
+ ? '/mo'
+ : props.cycle === 'quarterly'
+ ? '/qtr'
+ : props.cycle === 'semi_annual'
+ ? '/6mo'
+ : '/yr',
+)
+
+function priceLabel(price: number | undefined, isDefault: boolean | undefined): string {
+ // Defensive: VSelect's slots can fire with a stub item when model-value
+ // doesn't yet match any option (during hydration / async re-render).
+ if (typeof price !== 'number' || price === 0) return isDefault ? 'included' : 'no extra cost'
+ return `+$${price.toFixed(2)}${cycleSuffix.value}`
+}
+
+function onUpdate(v: unknown): void {
+ if (typeof v === 'string') emit('update:selected', v)
}
@@ -42,38 +70,50 @@ function priceLabel(v: DedicatedConfigValue): string {
{{ group.description }}
-