diff --git a/.changeset/curvy-moose-hide.md b/.changeset/curvy-moose-hide.md new file mode 100644 index 000000000..04eb45375 --- /dev/null +++ b/.changeset/curvy-moose-hide.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': patch +--- + +**fix**(`STabsPanel`): fix `background` reactivity in the provided `TabsPanelApi` diff --git a/.changeset/good-eels-jump.md b/.changeset/good-eels-jump.md new file mode 100644 index 000000000..30a0b71e7 --- /dev/null +++ b/.changeset/good-eels-jump.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': patch +--- + +**fix**(`STab`): don't destructure reactive `TabsPanelApi` diff --git a/.changeset/quiet-badgers-beg.md b/.changeset/quiet-badgers-beg.md new file mode 100644 index 000000000..bc58d3dca --- /dev/null +++ b/.changeset/quiet-badgers-beg.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': patch +--- + +**refactor**(`STextField`, `SSwitch`, `SUseNotification`, `SNotificationBody`, `SModal`, `SModalCard`, `SAlert`): set default values for optional props diff --git a/.changeset/quiet-ghosts-hug.md b/.changeset/quiet-ghosts-hug.md new file mode 100644 index 000000000..97131dbe4 --- /dev/null +++ b/.changeset/quiet-ghosts-hug.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': minor +--- + +**feature**(`SSwitch`): add `label-hidden` optional bool prop in order to _visually_ hide the `label` (`false` by default); update doc comments diff --git a/.changeset/spicy-rivers-run.md b/.changeset/spicy-rivers-run.md new file mode 100644 index 000000000..7f0969c46 --- /dev/null +++ b/.changeset/spicy-rivers-run.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': minor +--- + +**feature**(`STextField`, `SSwitch`): use auto-generated `id` if not provided diff --git a/.changeset/tricky-seas-knock.md b/.changeset/tricky-seas-knock.md new file mode 100644 index 000000000..e0537726e --- /dev/null +++ b/.changeset/tricky-seas-knock.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': patch +--- + +**perf**(`STextField`): use `computedEager` for cheap computeds; use `shallowRef` instead of `ref` diff --git a/.changeset/warm-zebras-breathe.md b/.changeset/warm-zebras-breathe.md new file mode 100644 index 000000000..0146fc7fd --- /dev/null +++ b/.changeset/warm-zebras-breathe.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': minor +--- + +**refactor!**(`STabsPanel`): use passive `model-value`; allow it to be `null` diff --git a/.eslintrc.js b/.eslintrc.js index 96f30949d..5fbdeed87 100755 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,11 +24,13 @@ module.exports = { '@typescript-eslint/consistent-type-definitions': 'off', - // FIXME - 'vue/require-default-prop': 'off', - - // FIXME - 'vuejs-accessibility/no-static-element-interactions': 'off', + 'vuejs-accessibility/label-has-for': [ + 'error', + { + // all labels should have `for` attr + required: 'id', + }, + ], }, overrides: [ { @@ -43,38 +45,57 @@ module.exports = { files: ['**/packages/ui/**/*.{ts,vue,js}'], extends: ['./packages/ui/.eslintrc-auto-import.json'], }, - { - files: ['**/*.spec.{js,ts}'], - env: { - jest: true, - }, - }, - - // It is OK to define a lot of components in stories or tests { files: ['**/packages/ui/stories/**/*.stories.ts', '**/*.cy.{js,ts}'], rules: { + // It is OK to define a lot of components in stories or tests 'vue/one-component-per-file': 'off', + + // We don't need such strictness in stories + 'vue/require-prop-types': 'off', }, }, { files: ['**/*.spec.ts', '**/*.spec.cy.ts'], rules: { 'max-nested-callbacks': 'off', + + // We don't need such strictness in tests + 'vue/require-default-prop': 'off', + 'vue/require-prop-types': 'off', }, }, - // FIXME - temporary disables to fix them in a different PRs { files: ['**/ui/src/components/Select/**/*.vue'], rules: { + // FIXME https://github.com/soramitsu/soramitsu-js-ui-library/issues/525 'vuejs-accessibility/click-events-have-key-events': 'off', + 'vuejs-accessibility/no-static-element-interactions': 'off', + }, + }, + + { + files: ['**/ui/src/components/DatePicker/**/*.vue'], + rules: { + // FIXME https://github.com/soramitsu/soramitsu-js-ui-library/issues/526 + 'vuejs-accessibility/no-static-element-interactions': 'off', + }, + }, + + { + files: ['**/ui/src/components/Table/**/*.vue'], + rules: { + // FIXME https://github.com/soramitsu/soramitsu-js-ui-library/issues/527 + 'vuejs-accessibility/no-static-element-interactions': 'off', }, }, + + // FIXME https://github.com/soramitsu/soramitsu-js-ui-library/issues/528 { - files: ['**/STextField.vue', '**/SSwitch.vue'], + files: ['**/ui/src/components/JsonInput/**/*.vue'], rules: { - 'vuejs-accessibility/label-has-for': 'off', + 'vuejs-accessibility/no-static-element-interactions': 'off', }, }, ], diff --git a/package.json b/package.json index 37f87c7a4..d3db56dfb 100755 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "scripts": { - "sb:serve": "pnpm --filter ui sb:serve", + "sb:dev": "pnpm --filter ui sb:dev", "sb:build": "pnpm --filter ui sb:build", "test:all": "run-s lint:check test:theme:unit build:vite-plugin-svg test:ui:unit test:ui:cy build:ui:only-vite test:ui:after-build", "test:theme:unit": "pnpm --filter theme test", @@ -44,8 +44,8 @@ "prettier": "^2.6.2", "prettier-eslint": "^15.0.0", "prettier-eslint-cli": "^7.1.0", - "typescript": "5.0.2", - "vite": "^4.1.4", - "vitest": "^0.29.2" + "typescript": "5.0.4", + "vite": "^4.3.4", + "vitest": "^0.30.1" } } diff --git a/packages/ui/.storybook/main.js b/packages/ui/.storybook/main.js deleted file mode 100644 index b123c6ede..000000000 --- a/packages/ui/.storybook/main.js +++ /dev/null @@ -1,14 +0,0 @@ -/** @type { import('@storybook/vue3-vite').StorybookConfig } */ -const config = { - // TODO use other stories as well - stories: ['../stories/**/Alert.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'], - framework: { - name: '@storybook/vue3-vite', - options: {}, - }, - docs: { - autodocs: 'tag', - }, -} -export default config diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts new file mode 100644 index 000000000..2408229fb --- /dev/null +++ b/packages/ui/.storybook/main.ts @@ -0,0 +1,19 @@ +import type { StorybookConfig } from '@storybook/vue3-vite' + +export default { + stories: [ + { + directory: '../stories/components', + titlePrefix: 'Components', + files: '**/*.stories.@(js|jsx|ts|tsx)', + }, + ], + addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'], + framework: { + name: '@storybook/vue3-vite', + options: {}, + }, + docs: { + autodocs: 'tag', + }, +} satisfies StorybookConfig diff --git a/packages/ui/.storybook/preview-head.html b/packages/ui/.storybook/preview-head.html deleted file mode 100644 index 05da1e9df..000000000 --- a/packages/ui/.storybook/preview-head.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/packages/ui/.storybook/preview.js b/packages/ui/.storybook/preview.ts similarity index 61% rename from packages/ui/.storybook/preview.js rename to packages/ui/.storybook/preview.ts index 13855337a..63d1465c3 100644 --- a/packages/ui/.storybook/preview.js +++ b/packages/ui/.storybook/preview.ts @@ -1,12 +1,10 @@ +import type { Preview } from '@storybook/vue3' + import 'virtual:windi.css' import './custom.scss' -/** @type { import('@storybook/vue3').Preview } */ -const preview = { +export default { parameters: { - backgrounds: { - default: 'light', - }, actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { @@ -15,6 +13,4 @@ const preview = { }, }, }, -} - -export default preview +} satisfies Preview diff --git a/packages/ui/cypress/component/SSwitch.spec.cy.ts b/packages/ui/cypress/component/SSwitch.spec.cy.ts index 657d884a9..2b79c1136 100644 --- a/packages/ui/cypress/component/SSwitch.spec.cy.ts +++ b/packages/ui/cypress/component/SSwitch.spec.cy.ts @@ -9,49 +9,58 @@ after(() => { VueTestUtils.config.global.components = {} }) -it('SSwitch - renders with specified label', () => { - cy.mount(SSwitch, { props: { id: 'id', label: 'Label' } }) +describe('SSwitch', () => { + it('renders with specified label', () => { + cy.mount(SSwitch, { props: { label: 'Label' } }) - cy.contains('Label') -}) + cy.contains('Label') + }) -it('SSwitch - renders unchecked switch by default', () => { - cy.mount(SSwitch, { props: { id: 'id' } }) + it('renders unchecked switch by default', () => { + cy.mount(SSwitch, { props: { label: 'Test' } }) - cy.get('input').should('be.not.checked') -}) + cy.get('input').should('be.not.checked') + }) -it('SSwitch - renders disabled switch when prop is passed', () => { - cy.mount(SSwitch, { props: { id: 'id', disabled: true } }) + it('renders disabled switch when prop is passed', () => { + cy.mount(SSwitch, { props: { label: 'Test', disabled: true } }) - cy.get('input').should('be.disabled') -}) + cy.get('input').should('be.disabled') + }) -it('SSwitch - handles two-way data binding and rises value up', () => { - cy.mount({ - setup() { - const checked = ref(false) + it('handles two-way data binding and rises value up', () => { + cy.mount({ + setup() { + const checked = ref(false) - return { checked } - }, - template: ` + return { checked } + }, + template: `
{{ checked }}
`, + }) + + cy.contains('Label').click() + cy.get('.switch').contains('true') }) - cy.contains('Label').click() - cy.get('.switch').contains('true') -}) + describe('a11y', () => { + beforeEach(() => { + cy.injectAxeAndConfigureCTDefaults() + }) -it('SSwitch - has the same id for linking label with input element', () => { - const id = 'identificator' - cy.mount(SSwitch, { props: { id } }) + it('SSwitch - a11y check with visible label', () => { + cy.mount(SSwitch, { props: { label: 'Test' } }) + cy.checkA11y() + }) - cy.get('input').should('have.id', id) - cy.get('label').should('have.attr', 'for', id) + it('SSwitch - a11y check with hidden label', () => { + cy.mount(SSwitch, { props: { label: 'Test', labelHidden: true } }) + cy.checkA11y() + }) + }) }) diff --git a/packages/ui/package.json b/packages/ui/package.json index f10ed3ca0..b5666b2cb 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@popperjs/core": "^2.10", + "@vue-kakuyaku/core": "^0.4", "@vueuse/core": "^9.13", "@vueuse/math": "^9.13", "date-fns": "^2.29", @@ -52,44 +53,43 @@ "@soramitsu-ui/icons": "0.1.0", "@soramitsu-ui/theme": "0.3.0", "@soramitsu-ui/vite-plugin-svg": "0.1.0", - "@storybook/addon-actions": "7.0.0-beta.62", - "@storybook/addon-essentials": "7.0.0-beta.62", - "@storybook/addon-interactions": "^7.0.0-beta.62", - "@storybook/addon-links": "7.0.0-beta.62", - "@storybook/blocks": "^7.0.0-beta.62", - "@storybook/cli": "7.0.0-beta.62", - "@storybook/testing-library": "^0.0.14-next.1", - "@storybook/vue3": "7.0.0-beta.62", - "@storybook/vue3-vite": "^7.0.0-beta.62", + "@storybook/addon-actions": "7.0.7", + "@storybook/addon-essentials": "7.0.7", + "@storybook/addon-interactions": "^7.0.7", + "@storybook/addon-links": "7.0.7", + "@storybook/blocks": "^7.0.7", + "@storybook/cli": "7.0.7", + "@storybook/testing-library": "^0.1.0", + "@storybook/vue3": "7.0.7", + "@storybook/vue3-vite": "^7.0.7", "@types/body-scroll-lock": "^3.1.0", "@types/jsoneditor": "^9.9.0", "@types/lodash-es": "^4.17.6", - "@vitejs/plugin-vue": "^4.0.0", - "@vitejs/plugin-vue-jsx": "^3.0.0", - "@vue/compiler-core": "^3.2", - "@vue/test-utils": "^2", + "@vitejs/plugin-vue": "^4.2.1", + "@vitejs/plugin-vue-jsx": "^3.0.1", + "@vue/test-utils": "^2.3.2", "axe-core": "^4.4.1", "body-scroll-lock": "^4.0.0-beta.0", - "cypress": "^12.7.0", + "cypress": "^12.11.0", "cypress-axe": "^1.0.0", "happy-dom": "^8.9.0", "immer": "^9.0.19", "postcss": "^8.4.16", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^3.18.0", - "rollup-plugin-dts": "^5.2.0", + "rollup": "^3.20.2", + "rollup-plugin-dts": "^5.3.0", "sass": "^1.41.1", - "storybook": "^7.0.0-beta.62", + "storybook": "^7.0.7", "tabbable": "^6.0.0", "ts-pattern": "^4.2.1", - "typescript": "5.0.2", + "typescript": "5.0.4", "unimport": "^3.0.2", "unplugin-auto-import": "^0.15.1", "unplugin-icons": "^0.15.3", - "vite": "^4.1.4", + "vite": "^4.3.4", "vite-plugin-windicss": "^1.8.10", - "vue-tsc": "^1.2.0", + "vue-tsc": "1.2", "windicss": "^3.5.4" } } diff --git a/packages/ui/src/components/Accordion/SAccordionItem.vue b/packages/ui/src/components/Accordion/SAccordionItem.vue index 0deef4292..7b2cb34ec 100644 --- a/packages/ui/src/components/Accordion/SAccordionItem.vue +++ b/packages/ui/src/components/Accordion/SAccordionItem.vue @@ -22,6 +22,7 @@ const props = withDefaults( const emit = defineEmits<(event: 'update:modelValue', value: boolean) => void>() +// FIXME replace with useVModel-passive? const model = ref(props.modelValue) watch( () => props.modelValue, diff --git a/packages/ui/src/components/Alert/SAlert.vue b/packages/ui/src/components/Alert/SAlert.vue index 789d9549f..120e9a052 100644 --- a/packages/ui/src/components/Alert/SAlert.vue +++ b/packages/ui/src/components/Alert/SAlert.vue @@ -13,6 +13,8 @@ type Props = { const props = withDefaults(defineProps(), { status: Status.Info, showCloseBtn: false, + title: undefined, + description: undefined, }) const emit = defineEmits<(event: 'click:close') => void>() diff --git a/packages/ui/src/components/Modal/SModal.vue b/packages/ui/src/components/Modal/SModal.vue index bd41f0ee6..123fbe922 100644 --- a/packages/ui/src/components/Modal/SModal.vue +++ b/packages/ui/src/components/Modal/SModal.vue @@ -111,6 +111,12 @@ const props = withDefaults(defineProps(), { // here is a Vue typing error - primitive value factory is a valid default value uniqueElementId as unknown as string, describedBy: null, + rootClass: undefined, + modalClass: undefined, + overlayClass: undefined, + rootStyle: undefined, + modalStyle: undefined, + overlayStyle: undefined, }) const emit = defineEmits(['update:show', 'click:overlay', 'before-open', 'after-open', 'before-close', 'after-close']) @@ -248,7 +254,10 @@ useCloseOnEsc( v-bind="overlayTransitionAttrs" v-on="overlayTransitionListeners" > - + +
(), { close: true, + title: undefined, }, ) diff --git a/packages/ui/src/components/Notifications/SNotificationBody.vue b/packages/ui/src/components/Notifications/SNotificationBody.vue index 3aca544f9..20bfd0833 100644 --- a/packages/ui/src/components/Notifications/SNotificationBody.vue +++ b/packages/ui/src/components/Notifications/SNotificationBody.vue @@ -14,6 +14,8 @@ const props = withDefaults( { status: Status.Info, timeout: 0, + title: undefined, + description: undefined, }, ) diff --git a/packages/ui/src/components/Notifications/SUseNotification.ts b/packages/ui/src/components/Notifications/SUseNotification.ts index d34071925..8bde7061a 100644 --- a/packages/ui/src/components/Notifications/SUseNotification.ts +++ b/packages/ui/src/components/Notifications/SUseNotification.ts @@ -1,8 +1,8 @@ import { h, defineComponent, type PropType, onScopeDispose } from 'vue' import { useVModel } from '@vueuse/core' +import { useParamScope } from '@vue-kakuyaku/core' import { Status } from '@/types' import SNotificationBody from './SNotificationBody.vue' -import { useConditionalScope } from '@/composables/conditional-scope' import { forceInject } from '@/util' import { NOTIFICATIONS_API_KEY } from './api' @@ -10,7 +10,7 @@ export default /* @__PURE__ */ defineComponent({ name: 'SUseNotification', props: { show: Boolean, - title: String, + title: { type: String, default: undefined }, status: { type: String as PropType, default: Status.Info, @@ -20,9 +20,14 @@ export default /* @__PURE__ */ defineComponent({ default: 5000, }, showCloseBtn: Boolean, - description: String, + description: { type: String, default: undefined }, }, - emits: ['update:show', 'click:close', 'timeout'], + emits: [ + // FIXME avoid `v-model` for `show`, because it always emits `false` from the component + 'update:show', + 'click:close', + 'timeout', + ], setup(props, { emit, slots }) { const show = useVModel(props, 'show', emit) @@ -38,7 +43,7 @@ export default /* @__PURE__ */ defineComponent({ emit('click:close') } - useConditionalScope(show, () => { + useParamScope(show, () => { const unreg = toasts.register({ slot: () => { const { show: _show, ...rest } = props diff --git a/packages/ui/src/components/Select/SDropdown.vue b/packages/ui/src/components/Select/SDropdown.vue index 531a33eef..d97ee027d 100644 --- a/packages/ui/src/components/Select/SDropdown.vue +++ b/packages/ui/src/components/Select/SDropdown.vue @@ -12,9 +12,9 @@ const props = defineProps<{ multiple?: boolean label?: string size?: SelectSize - inline?: boolean noAutoClose?: boolean loading?: boolean + inline?: boolean dropdownSearch?: boolean remoteSearch?: boolean }>() diff --git a/packages/ui/src/components/Select/SSelect.vue b/packages/ui/src/components/Select/SSelect.vue index 025540f35..0ad7d6e7f 100644 --- a/packages/ui/src/components/Select/SSelect.vue +++ b/packages/ui/src/components/Select/SSelect.vue @@ -14,8 +14,12 @@ const props = defineProps<{ size?: SelectSize noAutoClose?: boolean loading?: boolean + // FIXME `triggerSearch` makes sense only in scope of `SSelectBase` + // I think here it is better to name it `search-in-input` triggerSearch?: boolean + // FIXME `search-in-dropdown`? dropdownSearch?: boolean + // FIXME `search-external` remoteSearch?: boolean }>() @@ -27,6 +31,7 @@ const defaultOptionType = computed(() => (props.multiple ? SelectOptionType.Chec v-bind="{ ...$attrs, ...$props } as any" same-width-popper > +