diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md
index ac72b99ec..b438c6424 100644
--- a/packages/ui/CHANGELOG.md
+++ b/packages/ui/CHANGELOG.md
@@ -1,5 +1,23 @@
# @soramitsu-ui/ui
+## 0.13.13
+
+### Patch Changes
+
+- b5029a5b: **feat**(`SSelectDropdown`): add mandatory prop
+
+## 0.13.12
+
+### Patch Changes
+
+- 0a615d5c: **feat**(`STextField`): add validations list prop
+
+## 0.13.11
+
+### Patch Changes
+
+- 8d0d2d66: **fix**(`SDropdown`): add expression to display dropdown options correctly
+
## 0.13.10
### Patch Changes
diff --git a/packages/ui/cypress/component/STextField.spec.cy.ts b/packages/ui/cypress/component/STextField.spec.cy.ts
index 08bfff226..f686cfe2f 100644
--- a/packages/ui/cypress/component/STextField.spec.cy.ts
+++ b/packages/ui/cypress/component/STextField.spec.cy.ts
@@ -347,3 +347,67 @@ describe('Passing extra attributes', () => {
findInput().should('have.attr', 'data-count', 1)
})
})
+
+it('Validations list works', () => {
+ cy.mount({
+ setup() {
+ const model = ref('')
+
+ // since we don't change model value by using type() method we have to edit it manually
+ // because message slot relies on model.value correctness otherwise it's always empty
+ setTimeout(() => {
+ // matches some rules
+ model.value = 'a1'
+ }, 1000)
+
+ setTimeout(() => {
+ // matches every rule
+ model.value = 'a1!'
+ }, 2000)
+
+ function validations(value: string) {
+ return [
+ {
+ rule: /[a-z]/.test(value),
+ message: 'At least 1 lowercase letter',
+ },
+ {
+ rule: /\d/.test(value),
+ message: 'At least 1 digit',
+ },
+ {
+ rule: /[!"#$%&'()*+,./:;<=>?@[\]^_`{|}~\\-]/.test(value),
+ message: 'At least 1 special character',
+ },
+ ]
+ }
+
+ const validationsList = computed(() => {
+ return {
+ title: 'String must contain:',
+ validations: validations(model.value).map((v) => ({ isMatching: v.rule, ...v })),
+ showOnFocusOnly: true,
+ }
+ })
+
+ return {
+ validationsList,
+ model,
+ }
+ },
+ template: ``,
+ })
+
+ findMsg().should('not.exist')
+ findInput().focus()
+ findMsg().should('exist')
+ // first timeout
+ findInput().should('have.value', 'a1')
+ findInput().blur()
+ findMsg().should('not.exist')
+ findInput().focus()
+ findMsg().should('exist')
+ // second timeout
+ findInput().should('have.value', 'a1!')
+ findMsg().should('not.exist')
+})
diff --git a/packages/ui/cypress/component/Select.spec.cy.ts b/packages/ui/cypress/component/Select.spec.cy.ts
index 25af419c9..7eba67a60 100644
--- a/packages/ui/cypress/component/Select.spec.cy.ts
+++ b/packages/ui/cypress/component/Select.spec.cy.ts
@@ -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: `
+
Value: {{ model }}
+
+
+ `,
+ })
+
+ 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: `
+ Value: {{ model }}
+
+ `,
+ })
+
+ 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() {
diff --git a/packages/ui/etc/api/ui.api.md b/packages/ui/etc/api/ui.api.md
index 6f65b65b3..5caef6ada 100644
--- a/packages/ui/etc/api/ui.api.md
+++ b/packages/ui/etc/api/ui.api.md
@@ -33,6 +33,7 @@ import type { TableColumnRowSelectableFunc as TableColumnRowSelectableFunc_2 } f
import type { TableColumnSortBy as TableColumnSortBy_2 } from '@/components/Table/types';
import type { TableColumnSortOrder as TableColumnSortOrder_2 } from '@/components/Table/types';
import { UnwrapRef } from 'vue';
+import type { ValidationsList } from '@/components/TextField/types';
import { VNodeProps } from 'vue';
// @public (undocumented)
@@ -647,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[] | SelectOptionGroup[] | undefined;
@@ -661,12 +663,14 @@ loading?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
+mandatory?: boolean | undefined;
}>>>, {}, {}>, {
label?(_: {
options: UnwrapRef[] | SelectOptionGroup[]>;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
@@ -702,6 +706,8 @@ export interface SelectApi extends UnwrapRef> {
readonly label: string | null;
// (undocumented)
readonly loading: boolean;
+ // (undocumented)
+ readonly mandatory: boolean;
menuToggle: (value?: boolean) => void;
// (undocumented)
readonly multiple: boolean;
@@ -1283,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[] | SelectOptionGroup[] | undefined;
@@ -1297,12 +1304,14 @@ triggerSearch?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
+mandatory?: boolean | undefined;
}>>>, {}, {}>, {
label?(_: {
options: UnwrapRef[] | SelectOptionGroup[]>;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
@@ -1357,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;
@@ -1389,6 +1399,7 @@ sameWidthPopper: boolean;
triggerSearch: boolean;
dropdownSearch: boolean;
remoteSearch: boolean;
+mandatory: boolean;
}>>> & {
"onUpdate:modelValue"?: ((value: any) => any) | undefined;
onSearch?: ((value: string) => any) | undefined;
@@ -1400,6 +1411,7 @@ size: SelectSize;
disabled: boolean;
loading: boolean;
options: SelectOption[] | SelectOptionGroup[];
+mandatory: boolean;
syncMenuAndInputWidths: boolean;
noAutoClose: boolean;
sameWidthPopper: boolean;
@@ -1436,6 +1448,7 @@ type: SelectButtonType;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
@@ -1481,6 +1494,7 @@ search: boolean;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 67b3385ac..aad6bdac6 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@soramitsu-ui/ui",
- "version": "0.13.10",
+ "version": "0.13.13",
"main": "dist/lib.cjs",
"module": "dist/lib.mjs",
"types": "dist/lib.d.ts",
diff --git a/packages/ui/src/components/Popover/SPopoverWrappedTransition.vue b/packages/ui/src/components/Popover/SPopoverWrappedTransition.vue
index af091cd41..86d44d0b1 100644
--- a/packages/ui/src/components/Popover/SPopoverWrappedTransition.vue
+++ b/packages/ui/src/components/Popover/SPopoverWrappedTransition.vue
@@ -1,14 +1,10 @@
-
-
-
@@ -279,7 +302,7 @@ const inputType = computed(() =>
:disabled="disabled"
v-bind="inputAttrs()"
@input="onInput"
- @focus="isFocused = true"
+ @focus="handleFocus"
@blur="isFocused = false"
>
@@ -320,11 +343,37 @@ const inputType = computed(() =>
>
-
+
+
{{ props.validationsList.title }}
+
+
+
+
+
+ -
+
+
+
+ {{ item.message }}
+
+
+
+
+
+
{{ message }}
@@ -465,6 +514,12 @@ $theme-content-tertiary: theme.token-as-var('sys.color.content-tertiary');
opacity: 0;
}
}
+
+ &-requirement {
+ &_matched {
+ color: theme.token-as-var('sys.color.status.success');
+ }
+ }
}
&__eye {
diff --git a/packages/ui/src/components/TextField/types.ts b/packages/ui/src/components/TextField/types.ts
new file mode 100644
index 000000000..e5adba335
--- /dev/null
+++ b/packages/ui/src/components/TextField/types.ts
@@ -0,0 +1,10 @@
+export interface ValidationResultEntity {
+ isMatching: boolean
+ message: string
+}
+
+export interface ValidationsList {
+ validations: ValidationResultEntity[]
+ title: string
+ showOnFocusOnly?: boolean
+}
diff --git a/packages/ui/src/components/Tooltip/STooltip.vue b/packages/ui/src/components/Tooltip/STooltip.vue
index c451b71d6..9e6b4dacb 100644
--- a/packages/ui/src/components/Tooltip/STooltip.vue
+++ b/packages/ui/src/components/Tooltip/STooltip.vue
@@ -1,14 +1,10 @@
-
-
-