Skip to content

Commit

Permalink
Merge pull request #605 from soramitsu/feature/Add-Select-mandatory-prop
Browse files Browse the repository at this point in the history
  • Loading branch information
VladislavPopovSR authored Aug 7, 2024
2 parents b747be5 + 45bd464 commit 6175243
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 5 deletions.
6 changes: 6 additions & 0 deletions packages/ui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @soramitsu-ui/ui

## 0.13.13

### Patch Changes

- b5029a5b: **feat**(`SSelectDropdown`): add mandatory prop

## 0.13.12

### Patch Changes
Expand Down
121 changes: 121 additions & 0 deletions packages/ui/cypress/component/Select.spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,127 @@ it('SSelect - clicking options, checking auto-transformations', () => {
assertValue('2')
})

it('SSelect simple options mandatory mode works', () => {
cy.mount({
setup() {
const model = ref(null)

const options = [
{ label: 'Opt 1', value: 1 },
{ label: 'Opt 2', value: 2 },
{ label: 'Opt 3', value: 3 },
]

const [multiple, toggleMultiple] = useToggle(false)

return { model, options, multiple, toggleMultiple }
},
template: `
<div>Value: {{ model }}</div>
<button @click="toggleMultiple()">multiple : {{multiple}}</button>
<SSelect
v-model="model"
v-bind="{ options, multiple }"
label="Dap"
mandatory
/>
`,
})

function assertValue(val: string) {
cy.contains(`Value: ${val}`)
}

cy.contains('Dap').click()
cy.get('.s-select-option__content').eq(0).click()
assertValue('1')

cy.contains('Dap').click()
cy.get('.s-select-option__content').eq(0).click()
assertValue('1')

cy.get('button').contains('multiple').click()
cy.contains('Dap').click()
cy.get('.s-select-option__content').eq(1).click()
assertValue('[ 1, 2 ]')

cy.get('.s-select-option__content').eq(1).click()
cy.get('.s-select-option__content').eq(0).click()
assertValue('[ 1 ]')
})

it('SSelect group options mandatory mode works', () => {
cy.mount({
setup() {
const model = ref(null)

const options = [
{
header: '1st group',
selectAllBtn: true,
items: [
{
label: 'Germany',
value: 'du',
},
{
label: 'England',
value: 'en',
},
{
label: 'United Arab Emirates',
value: 'ae',
},
],
},
{
header: '2nd group',
selectAllBtn: false,
items: [
{
label: 'Iceland',
value: 'is',
},
{
label: 'Japan',
value: 'jp',
},
],
},
]

return { model, options }
},
template: `
<div>Value: {{ model }}</div>
<SSelect
v-model="model"
v-bind="{ options }"
label="Dap"
mandatory
multiple
/>
`,
})

function assertValue(val: string) {
cy.contains(`Value: ${val}`)
}

cy.contains('Dap').click()

cy.get('.s-select-dropdown__action').click()
assertValue(`[ "du", "en", "ae" ]`)
cy.get('.s-select-dropdown__action').click()
assertValue(`[ "du", "en", "ae" ]`)
cy.get('.s-select-option__content').eq(3).click()
assertValue(`[ "du", "en", "ae", "is" ]`)
cy.get('.s-select-dropdown__action').click()
assertValue(`[ "is" ]`)
cy.get('.s-select-option__content').eq(3).click()
assertValue(`[ "is" ]`)
})

it('SDropdown - model usage works', () => {
cy.mount({
setup() {
Expand Down
13 changes: 13 additions & 0 deletions packages/ui/etc/api/ui.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ loading?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
mandatory?: boolean | undefined;
}>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, VNodeProps & AllowedComponentProps & ComponentCustomProps, Readonly<globalThis.ExtractPropTypes<__VLS_TypePropsToRuntimeProps_10<{
modelValue?: any;
options?: SelectOption<any>[] | SelectOptionGroup<any>[] | undefined;
Expand All @@ -662,12 +663,14 @@ loading?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
mandatory?: boolean | undefined;
}>>>, {}, {}>, {
label?(_: {
options: UnwrapRef<SelectOption<any>[] | SelectOptionGroup<any>[]>;
multiple: boolean;
disabled: boolean;
loading: boolean;
mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
Expand Down Expand Up @@ -703,6 +706,8 @@ export interface SelectApi<T> extends UnwrapRef<UseSelectModelReturn<T>> {
readonly label: string | null;
// (undocumented)
readonly loading: boolean;
// (undocumented)
readonly mandatory: boolean;
menuToggle: (value?: boolean) => void;
// (undocumented)
readonly multiple: boolean;
Expand Down Expand Up @@ -1284,6 +1289,7 @@ triggerSearch?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
mandatory?: boolean | undefined;
}>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, VNodeProps & AllowedComponentProps & ComponentCustomProps, Readonly<globalThis.ExtractPropTypes<__VLS_TypePropsToRuntimeProps_9<{
modelValue?: any;
options?: SelectOption<any>[] | SelectOptionGroup<any>[] | undefined;
Expand All @@ -1298,12 +1304,14 @@ triggerSearch?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
mandatory?: boolean | undefined;
}>>>, {}, {}>, {
label?(_: {
options: UnwrapRef<SelectOption<any>[] | SelectOptionGroup<any>[]>;
multiple: boolean;
disabled: boolean;
loading: boolean;
mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
Expand Down Expand Up @@ -1358,6 +1366,7 @@ sameWidthPopper: boolean;
triggerSearch: boolean;
dropdownSearch: boolean;
remoteSearch: boolean;
mandatory: boolean;
}>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
"update:modelValue": (value: any) => void;
search: (value: string) => void;
Expand Down Expand Up @@ -1390,6 +1399,7 @@ sameWidthPopper: boolean;
triggerSearch: boolean;
dropdownSearch: boolean;
remoteSearch: boolean;
mandatory: boolean;
}>>> & {
"onUpdate:modelValue"?: ((value: any) => any) | undefined;
onSearch?: ((value: string) => any) | undefined;
Expand All @@ -1401,6 +1411,7 @@ size: SelectSize;
disabled: boolean;
loading: boolean;
options: SelectOption<any>[] | SelectOptionGroup<any>[];
mandatory: boolean;
syncMenuAndInputWidths: boolean;
noAutoClose: boolean;
sameWidthPopper: boolean;
Expand Down Expand Up @@ -1437,6 +1448,7 @@ type: SelectButtonType;
multiple: boolean;
disabled: boolean;
loading: boolean;
mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
Expand Down Expand Up @@ -1482,6 +1494,7 @@ search: boolean;
multiple: boolean;
disabled: boolean;
loading: boolean;
mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@soramitsu-ui/ui",
"version": "0.13.12",
"version": "0.13.13",
"main": "dist/lib.cjs",
"module": "dist/lib.mjs",
"types": "dist/lib.d.ts",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/Select/SDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const props = defineProps<{
dropdownSearch?: boolean
remoteSearch?: boolean
maxShownOptions?: string | number | undefined
mandatory?: boolean
}>()
const buttonType = computed(() => (props.inline ? SelectButtonType.Inline : SelectButtonType.Default))
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/Select/SSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const props = defineProps<{
dropdownSearch?: boolean
remoteSearch?: boolean
maxShownOptions?: string | number | undefined
mandatory?: boolean
}>()
const defaultOptionType = computed(() => (props.multiple ? SelectOptionType.Checkbox : SelectOptionType.Radio))
Expand Down
8 changes: 5 additions & 3 deletions packages/ui/src/components/Select/SSelectBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ const props = withDefaults(
label?: string | null
/**
* TODO
*
* - Doesn't allow to unselect value in single mode
* - Doesn't allow to unselect last the only one picked value in multiple mode
* TODO
* - Picks some value automatically (the first one) if `modelValue` is null and there are some available options
*/
mandatory?: boolean
Expand Down Expand Up @@ -83,6 +82,7 @@ const props = withDefaults(
triggerSearch: false,
dropdownSearch: false,
remoteSearch: false,
mandatory: false,
},
)
Expand All @@ -92,7 +92,7 @@ const emit = defineEmits<{
}>()
const model = useVModel(props, 'modelValue', emit)
const { multiple, disabled, loading, options, size, label, noAutoClose, remoteSearch } = toRefs(props)
const { multiple, disabled, loading, options, size, label, noAutoClose, remoteSearch, mandatory } = toRefs(props)
const modeling = useSelectModel({
model,
Expand All @@ -101,6 +101,7 @@ const modeling = useSelectModel({
storeSelectedOptions: remoteSearch,
singleModeAutoClose: not(noAutoClose),
onAutoClose: () => togglePopper(false),
mandatory,
})
const [showPopper, togglePopper] = useToggle(false)
Expand All @@ -125,6 +126,7 @@ const api: SelectApi<any> = reactive({
options,
disabled,
loading,
mandatory,
label,
isMenuOpened: showPopper,
menuToggle: togglePopper,
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/Select/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface SelectApi<T> extends UnwrapRef<UseSelectModelReturn<T>> {
readonly multiple: boolean
readonly disabled: boolean
readonly loading: boolean
readonly mandatory: boolean
readonly label: string | null
readonly size: SelectSize
readonly noAutoClose: boolean
Expand Down
16 changes: 15 additions & 1 deletion packages/ui/src/components/Select/use-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface UseSelectModelParams<T> {
* Should be used to actually perform menu closing
*/
onAutoClose: () => void
mandatory?: Ref<boolean>
}

export function useSelectModel<T = any>({
Expand All @@ -51,6 +52,7 @@ export function useSelectModel<T = any>({
singleModeAutoClose,
onAutoClose,
storeSelectedOptions,
mandatory,
}: UseSelectModelParams<T>): UseSelectModelReturn<T> {
const triggerAutoClose = () => singleModeAutoClose.value && onAutoClose()

Expand Down Expand Up @@ -177,13 +179,25 @@ export function useSelectModel<T = any>({
}

function toggleSelection(value: T): void {
isValueSelected(value) ? unselect(value) : select(value)
if (!isValueSelected(value)) {
select(value)
} else {
if (
mandatory?.value &&
((Array.isArray(model.value) && model.value.length === 1) || (model.value && !multiple.value))
)
return

unselect(value)
}
}

function toggleGroupSelection(optionGroup: SelectOptionGroup<T>): void {
const optionGroupValues = optionGroup.items.map((x) => x.value)

if (isGroupSelected(optionGroup)) {
if (mandatory?.value && Array.isArray(model.value) && model.value.length === optionGroup.items.length) return

const optionGroupSet = new Set(optionGroupValues)
const newModel = modelAsArray.value.filter((x) => !optionGroupSet.has(x))
modelChangedManually = true
Expand Down
1 change: 1 addition & 0 deletions packages/ui/stories/components/select/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const COMMON_ARGS = {
noAutoClose: false,
dropdownSearch: false,
maxShownOptions: 0,
mandatory: false,
}

export const COMMON_ARG_TYPES = {
Expand Down

0 comments on commit 6175243

Please sign in to comment.