Skip to content

Commit

Permalink
fix(SfSelect): correct usage of v-model [SFUI2-1300] (#2933)
Browse files Browse the repository at this point in the history
  • Loading branch information
Szymon-dziewonski committed Jul 25, 2023
1 parent 6bf2b3f commit b227ddd
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-elephants-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@storefront-ui/vue': patch
---

Correct usage of `v-model` in SfSelect
31 changes: 20 additions & 11 deletions apps/docs/components/components/select.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ If you need to make this field required, it is crucial to communicate this inten
<!-- react -->
<<<../../preview/next/pages/showcases/Select/SelectSizes.tsx#source
<!-- end react -->

</Showcase>

### Custom chevron
Expand All @@ -40,6 +41,7 @@ You can replace the default chevron with your own custom content by using the <!
<!-- react -->
<<<../../preview/next/pages/showcases/Select/SelectCustomChevron.tsx#source
<!-- end react -->

</Showcase>

### Invalid state
Expand All @@ -54,15 +56,15 @@ You can replace the default chevron with your own custom content by using the <!
<!-- react -->
<<<../../preview/next/pages/showcases/Select/SelectInvalid.tsx#source
<!-- end react -->

</Showcase>

## Notes

All non-prop attributes and styles added to `SfSelect` component are passed directly to native input element. This means that you can add all of the input attributes directly to `SfSelect`. If you want to style the wrapper `div`, you can pass your classes via the `wrapperClassName` prop.
All non-prop attributes and styles added to `SfSelect` component are passed directly to native input element. This means that you can add all of the input attributes directly to `SfSelect`. If you want to style the wrapper `div`, you can pass your classes via the `wrapperClassName` prop.

If you only have a few options, consider using the [`Radio`](radio.html) component instead of `Select`.


## Accessibility notes

Since this component uses the native `<select>` element, it inherits its accessibility features. For example, keyboard users can focus the select with `tab`, open with `space`, navigate the options using `arrows`, close the options menu with `Escape`, and select an option with `Enter` or `Space`.
Expand All @@ -77,15 +79,20 @@ Since this component uses the native `<select>` element, it inherits its accessi

## Props

| Prop name | Type | Default value | Possible values |
| ---------------- | -------- | ------------- | -------------------------------------- |
| `value` | `string` | | |
| `size` | `SfSelectSize` | `'base'` | `'sm'`, `'base'`,`'lg'` |
| `disabled` | `boolean` | `false` | |
| `invalid` | `boolean` | `false` | |
| `required` | `boolean` | `false` | |
| `placeholder` | `string` | | |
| `wrapperClassName` | `string` | | |
| Prop name | Type | Default value | Possible values |
| ------------------- | ----------------- | ------------- | -------------------------------------- |
<!-- vue -->
| `modelValue` | `boolean | string[]` | | |
<!-- end vue -->
<!-- react -->
| `value` | `string` | | |
<!-- end react -->
| `size` | `SfSelectSize` | `'base'` | `'sm'`, `'base'`,`'lg'` |
| `disabled` | `boolean` | `false` | |
| `invalid` | `boolean` | `false` | |
| `required` | `boolean` | `false` | |
| `placeholder` | `string` | | |
| `wrapperClassName` | `string` | | |
<!-- react -->
| `onChange` | `Function` | | |
| `slotChevron` | `ReactNode` | | |
Expand All @@ -112,12 +119,14 @@ Since this component uses the native `<select>` element, it inherits its accessi

::: slot source
<SourceCode>

<!-- vue -->
<<<../../../packages/sfui/frameworks/vue/components/SfSelect/SfSelect.vue
<!-- end vue -->

<!-- react -->
<<< ../../../packages/sfui/frameworks/react/components/SfSelect/SfSelect.tsx
<!-- end react -->

</SourceCode>
:::
2 changes: 1 addition & 1 deletion apps/preview/nuxt/pages/showcases/Filters/Category.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<li v-for="(category, index) in categories" :key="category.key">
<SfListItem
size="sm"
as="a"
tag="a"
:href="category.link"
:class="[
'first-of-type:mt-2 rounded-md active:bg-primary-100',
Expand Down
5 changes: 3 additions & 2 deletions apps/preview/nuxt/pages/showcases/Filters/Color.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
<input v-model="selectedValues" :value="value" class="appearance-none peer" type="checkbox" />
<span
class="inline-flex items-center justify-center p-1 transition duration-300 rounded-full cursor-pointer ring-1 ring-neutral-200 ring-inset outline-offset-2 outline-secondary-600 peer-checked:ring-2 peer-checked:ring-primary-700 peer-hover:bg-primary-100 peer-[&:not(:checked):hover]:ring-primary-200 peer-active:bg-primary-200 peer-active:ring-primary-300 peer-disabled:cursor-not-allowed peer-disabled:bg-disabled-100 peer-disabled:opacity-50 peer-disabled:ring-1 peer-disabled:ring-disabled-200 peer-disabled:hover:ring-disabled-200 peer-checked:hover:ring-primary-700 peer-checked:active:ring-primary-700 peer-focus:outline"
><SfThumbnail size="sm" :class="value"
/></span>
>
<SfThumbnail size="sm" :class="value" />
</span>
</template>
<p>
<span class="mr-2 typography-text-sm">{{ label }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<template v-if="type === 'category'">
<ul class="mt-2 mb-6">
<li>
<SfListItem size="sm" as="button" type="button">
<SfListItem size="sm" tag="button" type="button">
<div class="flex items-center">
<SfIconArrowBack size="sm" class="text-neutral-500 mr-3" />
Back to {{ details[0].label }}
Expand All @@ -51,7 +51,7 @@
<li v-for="({ id, link, label, counter }, categoryIndex) in details" :key="id">
<SfListItem
size="sm"
as="a"
tag="a"
:href="link"
:class="[
'first-of-type:mt-2 rounded-md active:bg-primary-100',
Expand Down
3 changes: 2 additions & 1 deletion apps/preview/nuxt/pages/showcases/Filters/Sorting.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ const options = ref([
{ label: 'Customer Rating', value: 'customer rating' },
{ label: 'Bestsellers', value: 'bestsellers' },
]);
const selected = ref('');
const selected = ref(options.value[0].value);
</script>
2 changes: 1 addition & 1 deletion apps/preview/nuxt/pages/showcases/Select/SelectInvalid.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<label>
<span class="pb-1 text-sm font-medium text-neutral-900 font-body"> Label </span>
<SfSelect :invalid="true" placeholder="-- Select --">
<SfSelect invalid placeholder="-- Select --">
<option v-for="{ value, label } in options" :key="value" :value="value">
{{ label }}
</option>
Expand Down
34 changes: 14 additions & 20 deletions packages/sfui/frameworks/vue/components/SfSelect/SfSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export default {
};
</script>
<script lang="ts" setup>
import { ref, type PropType } from 'vue';
import { SfSelectSize, SfIconExpandMore, useFocusVisible } from '@storefront-ui/vue';
import { type PropType, computed } from 'vue';
import { SfSelectSize, SfIconExpandMore, useFocusVisible, useDisclosure } from '@storefront-ui/vue';
const props = defineProps({
size: {
Expand All @@ -28,7 +28,7 @@ const props = defineProps({
type: Boolean,
default: false,
},
value: {
modelValue: {
type: String,
default: '',
},
Expand All @@ -41,19 +41,13 @@ const emit = defineEmits<{
(event: 'update:modelValue', param: string): void;
}>();
const selected = ref(props.value);
const chevronRotated = ref(false);
const { isOpen, close, open } = useDisclosure();
const { isFocusVisible } = useFocusVisible();
const clickHandler = () => (chevronRotated.value = true);
const blurHandler = () => (chevronRotated.value = false);
const keydownHandler = () => (chevronRotated.value = true);
const changedValue = (event: Event) => {
selected.value = (event.target as HTMLSelectElement).value;
chevronRotated.value = false;
emit('update:modelValue', (event.target as HTMLSelectElement).value);
};
const modelProxy = computed({
get: () => props.modelValue,
set: (value: string) => emit('update:modelValue', value),
});
</script>

<template>
Expand All @@ -69,8 +63,8 @@ const changedValue = (event: Event) => {
>
<select
v-bind="$attrs"
:value="value || selected"
:required="required"
v-model="modelProxy"
:disabled="disabled"
:class="[
'appearance-none disabled:cursor-not-allowed cursor-pointer pl-4 pr-3.5 text-neutral-900 ring-inset focus:ring-primary-700 focus:ring-2 outline-none bg-transparent rounded-md ring-1 ring-neutral-300 hover:ring-primary-700 active:ring-2 active:ring-primary-700 disabled:bg-disabled-100 disabled:text-disabled-900 disabled:ring-disabled-200',
Expand All @@ -82,10 +76,10 @@ const changedValue = (event: Event) => {
},
]"
data-testid="select-input"
@blur="blurHandler"
@click="clickHandler"
@change="changedValue"
@keydown.space="keydownHandler"
@blur="close"
@change="close"
@click="open"
@keydown.space="open"
>
<option
v-if="placeholder"
Expand All @@ -111,7 +105,7 @@ const changedValue = (event: Event) => {
:class="[
'absolute -translate-y-1 pointer-events-none top-1/3 right-4 transition easy-in-out duration-0.5',
disabled ? 'text-disabled-500' : 'text-neutral-500',
chevronRotated ? 'rotate-180' : '',
isOpen ? 'rotate-180' : '',
]"
/>
</slot>
Expand Down

0 comments on commit b227ddd

Please sign in to comment.