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
>
+
(),
{
diff --git a/packages/ui/src/components/Switch/SSwitch.vue b/packages/ui/src/components/Switch/SSwitch.vue
index 0c6560501..61d00eb22 100644
--- a/packages/ui/src/components/Switch/SSwitch.vue
+++ b/packages/ui/src/components/Switch/SSwitch.vue
@@ -1,22 +1,29 @@
+
+ :for="finalId"
+ :class="['s-switch__label sora-tpg-p3', { 'sr-only': labelHidden }]"
+ >
+ {{ label }}
+
diff --git a/packages/ui/src/components/Tabs/STab.vue b/packages/ui/src/components/Tabs/STab.vue
index d4b8d3852..d9817ad1f 100644
--- a/packages/ui/src/components/Tabs/STab.vue
+++ b/packages/ui/src/components/Tabs/STab.vue
@@ -1,5 +1,5 @@
@@ -35,8 +30,8 @@ watch(
role="tab"
class="s-tab flex justify-center items-center sora-tpg-p2"
:disabled="disabled"
- :class="[{ 's-tab_active': tabIsActive }, `s-tab_background_${background}`]"
- @click="activateTab"
+ :class="[{ 's-tab_active': tabIsActive }, `s-tab_background_${api.background}`]"
+ @click="selectSelf"
>
diff --git a/packages/ui/src/components/Tabs/STabsPanel.vue b/packages/ui/src/components/Tabs/STabsPanel.vue
index 84dd59ffa..a6a2bcb9e 100644
--- a/packages/ui/src/components/Tabs/STabsPanel.vue
+++ b/packages/ui/src/components/Tabs/STabsPanel.vue
@@ -3,7 +3,7 @@ import { type TabsPanelApi, TABS_PANEL_API_KEY, type TabsPanelBackgroundType } f
const props = withDefaults(
defineProps<{
- modelValue: string
+ modelValue: string | null
background?: TabsPanelBackgroundType
}>(),
{
@@ -14,19 +14,21 @@ const props = withDefaults(
const emit = defineEmits(['update:modelValue'])
-const selectTab = (tab: string): void => {
- emit('update:modelValue', tab)
+const model = useVModel(props, 'modelValue', emit, { passive: true })
+
+const selectTab = (tab: string | null): void => {
+ model.value = tab
}
-const active = computed(() => props.modelValue)
-const tabState: TabsPanelApi = reactive({
- active: active,
+const api: TabsPanelApi = reactive({
+ active: model,
selectTab,
- background: props.background,
+ background: toRef(props, 'background'),
})
-provide(TABS_PANEL_API_KEY, tabState)
+provide(TABS_PANEL_API_KEY, api)
+
void
- background: TabsPanelBackgroundType
+ readonly active: string | null
+ readonly selectTab: (tab: string | null) => void
+ readonly background: TabsPanelBackgroundType
}
export const TABS_PANEL_BACKGROUND_TYPES = ['primary', 'secondary', 'none'] as const
diff --git a/packages/ui/src/components/TextField/STextField.vue b/packages/ui/src/components/TextField/STextField.vue
index 9281a8cd5..59982523e 100644
--- a/packages/ui/src/components/TextField/STextField.vue
+++ b/packages/ui/src/components/TextField/STextField.vue
@@ -9,6 +9,7 @@ import type { StyleValue } from 'vue'
import { Status } from '@/types'
import { STATUS_ICONS_MAP_16, IconEye, IconEyeOff } from '../icons'
import type { MaybeElementRef } from '@vueuse/core'
+import { useElementIdFallback } from '@/composables/element-id-fallback'
/**
* warning: don't use it inside of `Props`. Vue compiler determines it
@@ -42,7 +43,7 @@ type Props = {
label?: string
/**
- * Recommended for a11y
+ * Can be passed to override default auto-generated id
*/
id?: string
@@ -119,6 +120,11 @@ const props = withDefaults(defineProps
(), {
noEye: false,
noModelValueStrictSync: false,
filledState: false,
+ message: undefined,
+ status: undefined,
+ id: undefined,
+ modelValue: '',
+ label: undefined,
})
const emit = defineEmits<{
@@ -130,7 +136,7 @@ const slots = useSlots()
// ***
-const status = computed(() => {
+const status = computedEager(() => {
if (props.status) return props.status
if (props.success) return Status.Success
if (props.warning) return Status.Warning
@@ -148,13 +154,13 @@ function onInput(e: Event) {
}
}
-const isValueEmpty = computed(() => !model.value)
+const isValueEmpty = computedEager(() => !model.value)
const isFocused = ref(false)
-const labelTypographyClass = computed(() =>
+const labelTypographyClass = computedEager(() =>
!(props.filledState || isFocused.value) && isValueEmpty.value ? 'sora-tpg-p3' : 'sora-tpg-p4',
)
-const inputRef = ref(null)
+const inputRef = shallowRef(null)
function handleInputWrapperClick(event: MouseEvent) {
if (event.target !== document.activeElement) {
@@ -206,7 +212,7 @@ const counterConfig = computed(() => {
return null
})
-const counterText = computed(() => {
+const counterText = computedEager(() => {
const config = counterConfig.value
if (!config) return null
const { limit } = config
@@ -226,16 +232,20 @@ const inputAttrs = () => {
// APPEND
-const showEye = computed(() => props.password && !props.noEye)
+const showEye = computedEager(() => props.password && !props.noEye)
const isAppendSlotDefined = () => !!slots.append
const shouldRenderAppend = () => !!counterText.value || isAppendSlotDefined() || showEye.value
// EYE
const [forceRevealPassword, toggleForceReveal] = useToggle()
-const inputType = computed(() =>
+const inputType = computedEager(() =>
props.password && (!showEye.value || !forceRevealPassword.value) ? 'password' : 'text',
)
+
+// A11Y
+
+const finalId = useElementIdFallback(toRef(props, 'id'))
@@ -252,15 +262,16 @@ const inputType = computed(() =>
:style="rootStyle()"
:data-status="status"
>
-
-
+
+