11<script setup lang="ts">
2- import { } from ' vue'
2+ import { onMounted } from ' vue'
33import { useI18n } from ' vue-i18n'
4- import { Cloud , Monitor , Check } from ' lucide-vue-next'
5- import { Card , CardContent , CardDescription , CardHeader , CardTitle } from ' @/components/ui/card'
4+ import { Cloud , Monitor } from ' lucide-vue-next'
65import { Badge } from ' @/components/ui/badge'
7- import { cn } from ' @/lib/utils'
86
97// Props and model
108const modelValue = defineModel <string >({ required: true })
@@ -19,54 +17,32 @@ const platforms = [
1917 description: t (' mcpInstallations.wizard.platform.local.description' ),
2018 icon: Monitor ,
2119 available: true ,
22- recommended: false ,
23- features : [
20+ recommended: true ,
21+ details : [
2422 t (' mcpInstallations.wizard.platform.local.features.direct' ),
25- t (' mcpInstallations.wizard.platform.local.features.fast' ),
26- t (' mcpInstallations.wizard.platform.local.features.secure' )
23+ t (' mcpInstallations.wizard.platform.local.features.fast' )
2724 ]
2825 },
29- // {
30- // id: 'docker',
31- // name: t('mcpInstallations.wizard.platform.docker.name'),
32- // description: t('mcpInstallations.wizard.platform.docker.description'),
33- // icon: Server,
34- // available: false,
35- // recommended: false,
36- // features: [
37- // t('mcpInstallations.wizard.platform.docker.features.isolated'),
38- // t('mcpInstallations.wizard.platform.docker.features.portable'),
39- // t('mcpInstallations.wizard.platform.docker.features.scalable')
40- // ]
41- // },
4226 {
4327 id: ' cloud' ,
4428 name: t (' mcpInstallations.wizard.platform.cloud.name' ),
4529 description: t (' mcpInstallations.wizard.platform.cloud.description' ),
4630 icon: Cloud ,
4731 available: false ,
4832 recommended: false ,
49- features : [
33+ details : [
5034 t (' mcpInstallations.wizard.platform.cloud.features.managed' ),
51- t (' mcpInstallations.wizard.platform.cloud.features.scalable' ),
52- t (' mcpInstallations.wizard.platform.cloud.features.redundant' )
35+ t (' mcpInstallations.wizard.platform.cloud.features.scalable' )
5336 ]
5437 }
5538]
5639
57-
58- // Methods
59- const selectPlatform = (platformId : string ) => {
60- const platform = platforms .find (p => p .id === platformId )
61- if (platform ?.available ) {
62- modelValue .value = platformId
40+ // Set default to local when component mounts
41+ onMounted (() => {
42+ if (! modelValue .value ) {
43+ modelValue .value = ' local'
6344 }
64- }
65-
66- // Set default to local if not set
67- if (! modelValue .value ) {
68- modelValue .value = ' local'
69- }
45+ })
7046 </script >
7147
7248<template >
@@ -81,82 +57,81 @@ if (!modelValue.value) {
8157 </p >
8258 </div >
8359
84- <!-- Platform Options -->
85- <div class =" grid gap-4" >
86- <Card
87- v-for =" platform in platforms"
88- :key =" platform.id"
89- :class =" cn(
90- 'cursor-pointer transition-all duration-200 hover:shadow-md',
91- {
92- 'ring-2 ring-blue-500 bg-blue-50': modelValue === platform.id && platform.available,
93- 'opacity-50 cursor-not-allowed': !platform.available,
94- 'hover:border-blue-300': platform.available && modelValue !== platform.id
95- }
96- )"
97- @click =" selectPlatform(platform.id)"
98- >
99- <CardHeader >
100- <div class =" flex items-start justify-between" >
101- <div class =" flex items-center gap-3" >
102- <div
103- :class =" cn(
104- 'p-2 rounded-lg',
105- {
106- 'bg-blue-100 text-blue-600': modelValue === platform.id && platform.available,
107- 'bg-gray-100 text-gray-400': !platform.available,
108- 'bg-gray-100 text-gray-600': platform.available && modelValue !== platform.id
109- }
110- )"
111- >
112- <component :is =" platform.icon" class =" h-6 w-6" />
60+ <!-- Platform Selection -->
61+ <fieldset :aria-label =" t('mcpInstallations.wizard.platform.title')" >
62+ <div class =" space-y-4" >
63+ <div
64+ v-for =" platform in platforms"
65+ :key =" platform.id"
66+ :class =" [
67+ 'group relative block rounded-lg border-1 px-6 py-4 cursor-pointer transition-all duration-200',
68+ 'focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2',
69+ 'sm:flex sm:justify-between',
70+ // Use the same gray background pattern as ProgressBars
71+ platform.available
72+ ? modelValue === platform.id
73+ ? '!border-primary bg-muted/50'
74+ : 'border-gray-300 bg-white hover:border-gray-400'
75+ : '!border-gray-50 !bg-gray-50 opacity-60 cursor-not-allowed'
76+ ]"
77+ @click =" () => platform.available && (modelValue = platform.id)"
78+ >
79+ <input
80+ type =" radio"
81+ name =" platform"
82+ :value =" platform.id"
83+ v-model =" modelValue"
84+ :disabled =" !platform.available"
85+ class =" sr-only"
86+ />
87+
88+ <span class =" flex items-center relative z-10" >
89+ <span class =" flex items-center gap-3 mr-4" >
90+ <!-- Platform Icon -->
91+ <div :class =" [
92+ 'p-2 rounded-lg transition-colors',
93+ modelValue === platform.id && platform.available
94+ ? 'bg-primary/10 text-primary'
95+ : platform.available
96+ ? 'bg-gray-100 text-gray-600'
97+ : 'bg-gray-100 text-gray-400'
98+ ]" >
99+ <component :is =" platform.icon" class =" h-5 w-5" />
113100 </div >
114- <div >
115- <CardTitle class =" flex items-center gap-2" >
116- {{ platform.name }}
101+
102+ <!-- Platform Info -->
103+ <span class =" flex flex-col text-sm" >
104+ <span class =" flex items-center gap-2" >
105+ <span class =" font-medium text-base text-gray-900" >
106+ {{ platform.name }}
107+ </span >
117108 <Badge v-if =" platform.recommended && platform.available" variant =" default" class =" text-xs" >
118109 {{ t('labels.recommended') }}
119110 </Badge >
120111 <Badge v-if =" !platform.available" variant =" secondary" class =" text-xs" >
121112 {{ t('labels.comingSoon') }}
122113 </Badge >
123- </CardTitle >
124- <CardDescription class =" mt-1" >
125- {{ platform.description }}
126- </CardDescription >
127- </div >
128- </div >
129-
130- <!-- Selection indicator -->
131- <div
132- v-if =" modelValue === platform.id && platform.available"
133- class =" flex items-center justify-center w-6 h-6 bg-blue-500 rounded-full"
134- >
135- <Check class =" h-4 w-4 text-white" />
136- </div >
137- </div >
138- </CardHeader >
114+ </span >
115+ <span class =" text-sm mt-1 text-gray-600" >
116+ <span class =" block sm:inline" >{{ platform.details[0] }}</span >
117+ <span v-if =" platform.details[1]" class =" hidden sm:mx-1 sm:inline" aria-hidden =" true" >· ; </span >
118+ <span v-if =" platform.details[1]" class =" block sm:inline" >{{ platform.details[1] }}</span >
119+ </span >
120+ </span >
121+ </span >
122+ </span >
139123
140- <CardContent >
141- <!-- Features -->
142- <div class =" space-y-2" >
143- <h4 class =" text-sm font-medium text-gray-700" >
144- {{ t('mcpInstallations.wizard.platform.features') }}:
145- </h4 >
146- <ul class =" space-y-1" >
147- <li
148- v-for =" feature in platform.features"
149- :key =" feature"
150- class =" flex items-center gap-2 text-sm text-gray-600"
151- >
152- <div class =" w-1.5 h-1.5 bg-gray-400 rounded-full" ></div >
153- {{ feature }}
154- </li >
155- </ul >
124+ <!-- Selection Indicator -->
125+ <div
126+ v-if =" modelValue === platform.id && platform.available"
127+ class =" absolute top-4 right-4 w-6 h-6 bg-primary rounded-full flex items-center justify-center"
128+ >
129+ <svg class =" w-4 h-4 text-white" fill =" currentColor" viewBox =" 0 0 20 20" >
130+ <path fill-rule =" evenodd" d =" M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule =" evenodd" />
131+ </svg >
156132 </div >
157- </CardContent >
158- </Card >
159- </div >
160-
133+ </div >
134+ </div >
135+ </fieldset >
161136 </div >
162137</template >
0 commit comments