|
2 | 2 | <div class="relative w-full"> |
3 | 3 | <Select v-model="selected" :options="options" multiple custom labelKey="title" valueKey="_id" |
4 | 4 | :placeholder="showSuggestion ? '' : 'Select Phrase Bundles to save...'"> |
5 | | - <template #selected="{ |
6 | | - selectedOption, |
7 | | - selectedOptions, |
8 | | - multiple, |
9 | | - getOptionLabel, |
10 | | - selectedCount, |
11 | | - }"> |
12 | | - <div v-if="multiple && selectedOptions.length > 0" class="flex items-center gap-2 flex-wrap"> |
13 | | - <span class="text-xs text-gray-500 dark:text-gray-400">Selected:</span> |
14 | | - <div class="flex items-center gap-1 flex-wrap"> |
15 | | - <span v-for="(option, index) in selectedOptions" :key="index" |
16 | | - class="inline-flex items-center gap-1 pl-2 pr-1 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-500/20 text-blue-800 dark:text-blue-300 rounded"> |
17 | | - {{ resolveTitle(option, getOptionLabel) }} |
18 | | - <span role="button" tabindex="0" title="Remove" |
19 | | - class="inline-flex items-center justify-center rounded-full p-0.5 hover:bg-blue-200 dark:hover:bg-blue-500/30 transition-colors cursor-pointer" |
20 | | - @click.stop.prevent="removeSelected(option)" |
21 | | - @mousedown.stop.prevent |
22 | | - @keyup.enter.stop="removeSelected(option)"> |
23 | | - <i class="i-mdi-close text-[11px]" /> |
| 5 | + <template #selected="{ |
| 6 | + selectedOption, |
| 7 | + selectedOptions, |
| 8 | + multiple, |
| 9 | + getOptionLabel, |
| 10 | + selectedCount, |
| 11 | + }"> |
| 12 | + <div v-if="multiple && selectedOptions.length > 0" class="flex items-center gap-2 flex-wrap"> |
| 13 | + <span class="text-xs text-gray-500 dark:text-gray-400">Selected:</span> |
| 14 | + <div class="flex items-center gap-1 flex-wrap"> |
| 15 | + <span v-for="(option, index) in selectedOptions" :key="index" |
| 16 | + class="inline-flex items-center gap-1 pl-2 pr-1 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-500/20 text-blue-800 dark:text-blue-300 rounded"> |
| 17 | + {{ resolveTitle(option, getOptionLabel) }} |
| 18 | + <span role="button" tabindex="0" title="Remove" |
| 19 | + class="inline-flex items-center justify-center rounded-full p-0.5 hover:bg-blue-200 dark:hover:bg-blue-500/30 transition-colors cursor-pointer" |
| 20 | + @click.stop.prevent="removeSelected(option)" @mousedown.stop.prevent |
| 21 | + @keyup.enter.stop="removeSelected(option)"> |
| 22 | + <i class="i-mdi-close text-[11px]" /> |
| 23 | + </span> |
24 | 24 | </span> |
25 | | - </span> |
| 25 | + </div> |
26 | 26 | </div> |
27 | | - </div> |
28 | | - <span v-else-if="!multiple" class="flex items-center gap-2"> |
29 | | - <span class="inline-flex items-center justify-center w-5 h-5 text-xs text-blue-600"> |
30 | | - ✓ |
| 27 | + |
| 28 | + <span v-else-if="!multiple" class="flex items-center gap-2"> |
| 29 | + <span class="inline-flex items-center justify-center w-5 h-5 text-xs text-blue-600"> |
| 30 | + ✓ |
| 31 | + </span> |
| 32 | + <span class="font-medium">{{ resolveTitle(selectedOption, getOptionLabel) }}</span> |
31 | 33 | </span> |
32 | | - <span class="font-medium">{{ resolveTitle(selectedOption, getOptionLabel) }}</span> |
33 | | - </span> |
34 | | - </template> |
35 | | - <template #header> |
36 | | - <InputGroup class="w-full p-2"> |
37 | | - <Input v-model="searchedBundleName" tabindex="0" :disabled="isFetching" placeholder="Search bundles..." /> |
38 | | - <Button label="Create" color="secondary" :disabled="!isCreateNewAllowed || isFetching || isCreating" |
39 | | - :is-loading="isCreating" @click="createNewBundle" /> |
40 | | - </InputGroup> |
41 | | - </template> |
42 | | - <template #each="{ option, isSelected, setSelected }"> |
43 | | - <div :class="[ |
44 | | - 'px-3 py-2 text-sm cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-150', |
45 | | - isSelected |
46 | | - ? 'bg-primary text-white hover:bg-primary/90' |
47 | | - : 'text-gray-900 dark:text-gray-100', |
48 | | - ]" role="option" :aria-selected="isSelected" @click="setSelected"> |
49 | | - {{ (option as unknown as PhraseBundleType).title }} |
50 | | - </div> |
51 | | - </template> |
| 34 | + |
| 35 | + </template> |
| 36 | + <template #header> |
| 37 | + <InputGroup class="w-full p-2 justify-center"> |
| 38 | + <Input v-model="searchedBundleName" tabindex="0" :disabled="isFetching" placeholder="Search bundles..." /> |
| 39 | + <Button label="Create" color="secondary" :disabled="!isCreateNewAllowed || isFetching || isCreating" |
| 40 | + :is-loading="isCreating" @click="createNewBundle" /> |
| 41 | + </InputGroup> |
| 42 | + </template> |
| 43 | + <template #each="{ option, isSelected, setSelected }"> |
| 44 | + <div :class="[ |
| 45 | + 'px-3 py-2 text-sm cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-150', |
| 46 | + isSelected |
| 47 | + ? 'bg-primary text-white hover:bg-primary/90' |
| 48 | + : 'text-gray-900 dark:text-gray-100', |
| 49 | + ]" role="option" :aria-selected="isSelected" @click="setSelected"> |
| 50 | + {{ (option as unknown as PhraseBundleType).title }} |
| 51 | + </div> |
| 52 | + </template> |
52 | 53 | </Select> |
53 | 54 |
|
54 | 55 | <!-- |
|
58 | 59 | existing bundle, which clears the suggestion. |
59 | 60 | --> |
60 | 61 | <div v-if="showSuggestion" class="absolute inset-y-0 left-0 right-12 flex items-center pl-3 pointer-events-none"> |
61 | | - <div class="pointer-events-auto inline-flex items-center gap-1.5 min-w-0 max-w-full rounded-full bg-purple-100 dark:bg-purple-900/40 text-purple-800 dark:text-purple-200 ring-1 ring-purple-300 dark:ring-purple-700 px-2.5 py-1" |
| 62 | + <div |
| 63 | + class="pointer-events-auto inline-flex items-center gap-1.5 min-w-0 max-w-full rounded-full bg-purple-100 dark:bg-purple-900/40 text-purple-800 dark:text-purple-200 ring-1 ring-purple-300 dark:ring-purple-700 px-2.5 py-1" |
62 | 64 | @click.stop> |
63 | 65 | <i class="i-mdi-auto-fix text-sm opacity-80 shrink-0" /> |
64 | 66 | <span class="text-[10px] uppercase tracking-wide font-semibold opacity-70 shrink-0">Suggested</span> |
65 | 67 | <template v-if="!isEditingSuggested"> |
66 | 68 | <span class="text-sm font-medium whitespace-nowrap">{{ suggestedName }}</span> |
67 | | - <button type="button" class="shrink-0 opacity-70 hover:opacity-100" title="Edit name" @click.stop="startEditSuggested"> |
| 69 | + <button type="button" class="shrink-0 opacity-70 hover:opacity-100" title="Edit name" |
| 70 | + @click.stop="startEditSuggested"> |
68 | 71 | <i class="i-mdi-pencil text-xs" /> |
69 | 72 | </button> |
70 | 73 | </template> |
@@ -289,5 +292,36 @@ defineExpose({ |
289 | 292 | </script> |
290 | 293 |
|
291 | 294 | <style scoped> |
292 | | -/* No special styles needed; PilotUI components include styling */ |
| 295 | +/* |
| 296 | + SelectPhraseBundleV2 sits next to the lg "Save" button in SaveWordSectionV2. |
| 297 | + The pilotui Select renders its visible trigger button at the bottom of a |
| 298 | + 4-level wrapper chain, two links of which are display:block — so without help |
| 299 | + the trigger lands a few px shorter than Save and the selector field looks |
| 300 | + smaller (ClickUp 86exw6kme follow-up). Lay the root out as a flex row and |
| 301 | + stretch every link of the chain so the trigger fills the full row height and |
| 302 | + lines up with Save, in both the plain and freemium (InputGroup) save layouts. |
| 303 | + The suggested-bundle chip is position:absolute, so flex layout doesn't move it. |
| 304 | +
|
| 305 | + The chain selectors lean on pilotui's internal markup (Select root → .relative |
| 306 | + → .relative.w-full → button); if a pilotui upgrade changes that nesting this is |
| 307 | + a cosmetic few-px regression, not a functional break. |
| 308 | +*/ |
| 309 | +.relative.w-full { |
| 310 | + display: flex; |
| 311 | +} |
| 312 | +
|
| 313 | +/* Select root (already a column flex box) + the two block wrappers below it: |
| 314 | + grow to the row height and pass that height down as column flex boxes. */ |
| 315 | +.relative.w-full > :deep(.flex.flex-col), |
| 316 | +.relative.w-full :deep(.flex.flex-col > .relative), |
| 317 | +.relative.w-full :deep(.flex.flex-col > .relative > .relative) { |
| 318 | + display: flex; |
| 319 | + flex-direction: column; |
| 320 | + flex: 1 1 auto; |
| 321 | +} |
| 322 | +
|
| 323 | +/* The visible trigger button fills the stretched chain. */ |
| 324 | +.relative.w-full :deep(.flex.flex-col > .relative > .relative > button) { |
| 325 | + flex: 1 1 auto; |
| 326 | +} |
293 | 327 | </style> |
0 commit comments