Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[refactor] #513: enable ESLint a11y rules back #529

Merged
merged 4 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-badgers-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@soramitsu-ui/ui': patch
---

**refactor**(`STextField`, `SSwitch`, `SUseNotification`, `SNotificationBody`, `SModal`, `SModalCard`, `SAlert`): set default values for optional props
2 changes: 1 addition & 1 deletion .changeset/quiet-ghosts-hug.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'@soramitsu-ui/ui': minor
---

**refactor!**(`SSwitch`): remove `id` prop (it is generated automatically inside now); make `label` prop required - a11y first; add `label-hidden` optional bool prop in order to _visually_ hide the `label` (`false` by default); update doc comments
**feature**(`SSwitch`): add `label-hidden` optional bool prop in order to _visually_ hide the `label` (`false` by default); update doc comments
5 changes: 5 additions & 0 deletions .changeset/spicy-rivers-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@soramitsu-ui/ui': minor
---

**feature**(`STextField`, `SSwitch`): use auto-generated `id` if not provided
5 changes: 5 additions & 0 deletions .changeset/tricky-seas-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@soramitsu-ui/ui': patch
---

**perf**(`STextField`): use `computedEager` for cheap computeds; use `shallowRef` instead of `ref`
53 changes: 37 additions & 16 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand All @@ -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',
},
},
],
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/components/Alert/SAlert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type Props = {
const props = withDefaults(defineProps<Props>(), {
status: Status.Info,
showCloseBtn: false,
title: undefined,
description: undefined,
})

const emit = defineEmits<(event: 'click:close') => void>()
Expand Down
11 changes: 10 additions & 1 deletion packages/ui/src/components/Modal/SModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ const props = withDefaults(defineProps<Props>(), {
// 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'])
Expand Down Expand Up @@ -248,7 +254,10 @@ useCloseOnEsc(
v-bind="overlayTransitionAttrs"
v-on="overlayTransitionListeners"
>
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events -->
<!-- "click" handling is made purely for convenience of mouse users,
thus, this element should not be "accessible".
keyboard interaction is handled separately -->
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events vuejs-accessibility/no-static-element-interactions -->
<div
v-if="overlayIf"
:class="['s-modal__overlay', overlayClass]"
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/Modal/SModalCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ withDefaults(
}>(),
{
close: true,
title: undefined,
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const props = withDefaults(
{
status: Status.Info,
timeout: 0,
title: undefined,
description: undefined,
},
)

Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/Notifications/SUseNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Status>,
default: Status.Info,
Expand All @@ -20,7 +20,7 @@ export default /* @__PURE__ */ defineComponent({
default: 5000,
},
showCloseBtn: Boolean,
description: String,
description: { type: String, default: undefined },
},
emits: [
// FIXME avoid `v-model` for `show`, because it always emits `false` from the component
Expand Down
26 changes: 17 additions & 9 deletions packages/ui/src/components/Switch/SSwitch.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
<script setup lang="ts">
import { uniqueElementId } from '@/util'
import { useElementIdFallback } from '@/composables/element-id-fallback'

type Props = {
/**
* Switch state
*/
modelValue?: boolean
/**
* Text label for switch. Required for a11y. If you don't want it to be rendered, use {@link labelHidden}
*
* @default ''
* Explicit id instead of an auto-generated one
*/
id?: string
/**
* Text label for switch. Required for a11y. If you don't want it to be rendered, use {@link labelHidden}.
* You can also pass it as a slot.
*/
label: string
label?: string
/**
* Whether to render label or not
*
Expand All @@ -29,28 +33,32 @@ type Props = {
const props = withDefaults(defineProps<Props>(), {
disabled: false,
labelHidden: false,
id: undefined,
label: undefined,
})

const inputId = uniqueElementId()

const emit = defineEmits<(event: 'update:modelValue', value: boolean) => void>()
const model = useVModel(props, 'modelValue', emit)

const finalId = useElementIdFallback(toRef(props, 'id'))
</script>

<template>
<div class="s-switch">
<input
:id="inputId"
:id="finalId"
v-model="model"
type="checkbox"
:disabled="disabled"
class="s-switch__button"
>
<!-- note: `sr-only` class visually hides the element while keeping it accessible for Screen Reader -->
<label
:for="inputId"
:for="finalId"
:class="['s-switch__label sora-tpg-p3', { 'sr-only': labelHidden }]"
>{{ label }}</label>
>
<slot name="label">{{ label }}</slot>
</label>
</div>
</template>

Expand Down
35 changes: 23 additions & 12 deletions packages/ui/src/components/TextField/STextField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -42,7 +43,7 @@ type Props = {
label?: string

/**
* Recommended for a11y
* Can be passed to override default auto-generated id
*/
id?: string

Expand Down Expand Up @@ -119,6 +120,11 @@ const props = withDefaults(defineProps<Props>(), {
noEye: false,
noModelValueStrictSync: false,
filledState: false,
message: undefined,
status: undefined,
id: undefined,
modelValue: '',
label: undefined,
})

const emit = defineEmits<{
Expand All @@ -130,7 +136,7 @@ const slots = useSlots()

// ***

const status = computed<null | TextFieldStatus>(() => {
const status = computedEager<null | TextFieldStatus>(() => {
if (props.status) return props.status
if (props.success) return Status.Success
if (props.warning) return Status.Warning
Expand All @@ -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<MaybeElementRef>(null)
const inputRef = shallowRef<MaybeElementRef>(null)

function handleInputWrapperClick(event: MouseEvent) {
if (event.target !== document.activeElement) {
Expand Down Expand Up @@ -206,7 +212,7 @@ const counterConfig = computed<null | CounterConfig>(() => {
return null
})

const counterText = computed<string | null>(() => {
const counterText = computedEager<string | null>(() => {
const config = counterConfig.value
if (!config) return null
const { limit } = config
Expand All @@ -226,16 +232,20 @@ const inputAttrs = () => {

// APPEND

const showEye = computed<boolean>(() => props.password && !props.noEye)
const showEye = computedEager<boolean>(() => 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'))
</script>

<template>
Expand All @@ -252,15 +262,16 @@ const inputType = computed(() =>
:style="rootStyle()"
:data-status="status"
>
<!-- key events works with input element -->
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events -->
<!-- all interaction is made through `<input>`.
these mouse events are handled for mouse users convenience -->
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events vuejs-accessibility/no-static-element-interactions -->
<div
class="s-text-field__input-wrapper"
@click="handleInputWrapperClick"
@mousedown="handleInputWrapperMouseDown"
>
<label
:for="id"
:for="finalId"
:class="labelTypographyClass"
>
<slot name="label">{{ label }}</slot>
Expand All @@ -270,7 +281,7 @@ const inputType = computed(() =>
<slot name="prefix" />

<input
:id="id"
:id="finalId"
ref="inputRef"
class="sora-tpg-p3"
:value="model"
Expand Down
13 changes: 13 additions & 0 deletions packages/ui/src/composables/element-id-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type Ref } from 'vue'
import { uniqueElementId } from '@/util'

export function useElementIdFallback(optional: Ref<undefined | null | string>): Ref<string> {
let unique: string | undefined

return computedEager((): string => {
const opt = optional.value
if (opt) return opt
if (!unique) unique = uniqueElementId()
return unique
})
}