Skip to content

Commit 3e07e16

Browse files
committed
fixup! feat: add NcFormBoxSelectNative
1 parent 772cac3 commit 3e07e16

File tree

2 files changed

+54
-18
lines changed

2 files changed

+54
-18
lines changed

src/components/NcFormBox/NcFormBoxItem.vue

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const {
3333
class?: VueClassType
3434
/** Interactive item classes */
3535
itemClasses?: VueClassType
36+
/** Disable clickable overlay from the interactive item element to manually implement */
37+
pure?: boolean
3638
}>()
3739
3840
defineEmits<{
@@ -52,7 +54,15 @@ const slots = defineSlots<{
5254
descriptionId?: string
5355
}>
5456
/** Icon content */
55-
icon?: Slot
57+
icon?: Slot<{
58+
/** IDRef of the description element if present */
59+
descriptionId?: string
60+
}>
61+
/** Extra content slot for additional overlays */
62+
extra?: Slot<{
63+
/** IDRef of the description element if present */
64+
descriptionId?: string
65+
}>
5666
}>()
5767
5868
const { formBoxItemClass } = useNcFormBox()
@@ -72,18 +82,19 @@ const hasDescription = () => !!description || !!slots.description
7282
[$style.formBoxItem_legacy]: isLegacy,
7383
},
7484
]">
85+
<slot name="extra" :description-id />
7586
<span :class="$style.formBoxItem__content">
7687
<component
7788
:is="tag"
78-
:class="[$style.formBoxItem__element, itemClasses]"
89+
:class="[$style.formBoxItem__element, itemClasses, { [$style.formBoxItem__element_clickable]: !pure }]"
7990
v-bind="$attrs"
8091
@click="$emit('click', $event)">
8192
<slot :description-id>
8293
{{ label || '⚠️ Label is missing' }}
8394
</slot>
8495
</component>
8596
<span v-if="hasDescription()" :id="descriptionId" :class="$style.formBoxItem__description">
86-
<slot name="description">
97+
<slot name="description" :description-id>
8798
{{ description }}
8899
</slot>
89100
</span>
@@ -169,7 +180,7 @@ const hasDescription = () => !!description || !!slots.description
169180
170181
// A trick for accessibility:
171182
// make entire component clickable while internally splitting the interactive item and the description
172-
.formBoxItem__element::after {
183+
.formBoxItem__element_clickable::after {
173184
content: '';
174185
position: absolute;
175186
inset: 0;

src/components/NcFormBoxSelectNative/NcFormBoxSelectNative.vue

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
-->
55

66
<script setup lang="ts" generic="T extends string">
7-
import { computed, useId, useTemplateRef } from 'vue'
8-
import IconUnfoldMoreHorizontal from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
7+
import { mdiUnfoldMoreHorizontal } from '@mdi/js'
8+
import { computed, useTemplateRef } from 'vue'
99
import NcFormBoxItem from '../NcFormBox/NcFormBoxItem.vue'
10+
import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'
11+
import { createElementId } from '../../utils/createElementId.ts'
1012
1113
/** Selected value */
1214
const modelValue = defineModel<T>({ required: true })
@@ -27,9 +29,29 @@ const {
2729
disabled?: boolean
2830
}>()
2931
30-
const selectId = useId()
32+
const selectId = createElementId()
3133
const selectElement = useTemplateRef('select')
34+
3235
const selectedLabel = computed(() => options.find((option) => option.value === modelValue.value)?.label)
36+
37+
// .showPicker() is not available some browsers (e.g. Safari)
38+
// When the method is not available, we keep select overlay clickable not invisible
39+
// When the method is available, hidden select is not directly clickable but opens programmatically
40+
// The last approach looks slightly better without focusing select by click
41+
const isShowPickerAvailable = 'showPicker' in HTMLSelectElement.prototype
42+
43+
/**
44+
* Handle label click to open the native select picker if possible.
45+
*
46+
* @param event - Click event
47+
*/
48+
function onLabelClick(event: MouseEvent) {
49+
if (!isShowPickerAvailable) {
50+
return
51+
}
52+
event?.preventDefault()
53+
selectElement.value!.showPicker()
54+
}
3355
</script>
3456

3557
<template>
@@ -39,15 +61,18 @@ const selectedLabel = computed(() => options.find((option) => option.value === m
3961
:label
4062
:description="selectedLabel"
4163
:disabled
64+
:pure="!isShowPickerAvailable"
4265
inverted-accent
43-
@click.prevent="selectElement!.showPicker() /* Not available in old Safari */">
44-
<template #icon="{ descriptionId }">
45-
<IconUnfoldMoreHorizontal :size="20" />
66+
@click="onLabelClick">
67+
<template #icon>
68+
<NcIconSvgWrapper :path="mdiUnfoldMoreHorizontal" inline />
69+
</template>
70+
<template #extra="{ descriptionId }">
4671
<select
4772
:id="selectId"
4873
ref="select"
4974
v-model="modelValue"
50-
class="hidden-select"
75+
:class="[$style.hiddenSelect, { [$style.hiddenSelect_manual]: isShowPickerAvailable }]"
5176
:aria-describedby="descriptionId">
5277
<option v-for="option in options" :key="option.value" :value="option.value">
5378
{{ option.label }}
@@ -57,20 +82,20 @@ const selectedLabel = computed(() => options.find((option) => option.value === m
5782
</NcFormBoxItem>
5883
</template>
5984

60-
<!-- TODO: module -->
61-
<style scoped>
62-
.hidden-select {
85+
<style lang="scss" module>
86+
.hiddenSelect {
6387
position: absolute;
6488
inset: 0;
6589
margin: 0;
6690
height: auto;
91+
cursor: pointer;
6792
/* TODO: does it work well cross-browser? */
6893
opacity: 0;
69-
pointer-events: none;
94+
}
7095
71-
option {
72-
pointer-events: all;
73-
}
96+
// Select is open manual instead of opening by click on invisible select
97+
.hiddenSelect_manual {
98+
pointer-events: none;
7499
}
75100
</style>
76101

0 commit comments

Comments
 (0)