Skip to content

Commit 93e5540

Browse files
committed
chore(design-system, web-app-files, web-pkg, web-runtime): [OCISDEV-496] ui improvements
1 parent e280547 commit 93e5540

File tree

11 files changed

+87
-26
lines changed

11 files changed

+87
-26
lines changed

packages/design-system/src/components/OcDatepicker/__snapshots__/OcDatepicker.spec.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ exports[`OcDatePicker > renders 1`] = `
66
<!--v-if--> <input id="oc-textinput-1" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
77
<!--v-if-->
88
</div>
9-
<div class="oc-text-input-message">
9+
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
1010
<!--v-if--> <span id="oc-textinput-1-message" class=""></span>
1111
</div>
1212
<!--v-if-->

packages/design-system/src/components/OcIcon/OcIcon.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<inline-svg
1212
:src="nameWithFillType"
1313
:transform-source="transformSvgElement"
14+
:tabindex="accessibleLabel ? '0' : null"
1415
:aria-hidden="accessibleLabel === '' ? 'true' : null"
1516
:aria-labelledby="accessibleLabel === '' ? null : svgTitleId"
1617
:focusable="accessibleLabel === '' ? 'false' : null"

packages/design-system/src/components/OcTextInput/OcTextInput.spec.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,19 @@ describe('OcTextInput', () => {
5050
passwordPolicy = { active: false, pass: false }
5151
) {
5252
const passwordPolicyMock = mock<PasswordPolicy>()
53+
const policyRules = [
54+
{
55+
code: 'minLength',
56+
message: '%{param1}+ letters',
57+
format: ['8'],
58+
verified: passwordPolicy.pass
59+
}
60+
]
61+
passwordPolicyMock.rules = policyRules
5362
passwordPolicyMock.missing.mockReturnValue({
54-
rules: [
55-
{
56-
code: 'minLength',
57-
message: '%{param1}+ letters',
58-
format: ['8'],
59-
verified: passwordPolicy.pass
60-
}
61-
]
63+
rules: policyRules
6264
})
63-
passwordPolicyMock.check.mockReturnValueOnce(passwordPolicy.pass)
65+
passwordPolicyMock.check.mockReturnValue(passwordPolicy.pass)
6466

6567
if (passwordPolicy.active) {
6668
options.props = {

packages/design-system/src/components/OcTextInput/OcTextInput.vue

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@
4646
</oc-button>
4747
</div>
4848
<div
49-
v-if="showMessageLine"
49+
v-if="showMessageLine || errorMessage || warningMessage"
5050
class="oc-text-input-message"
5151
:class="{
5252
'oc-text-input-description': !!descriptionMessage,
5353
'oc-text-input-warning': !!warningMessage,
5454
'oc-text-input-danger': !!errorMessage
5555
}"
56+
:aria-live="errorMessage || warningMessage ? 'assertive' : 'off'"
57+
aria-atomic="true"
58+
role="alert"
5659
>
5760
<oc-icon
5861
v-if="messageText !== null && !!descriptionMessage"
@@ -207,9 +210,20 @@ const additionalListeners = computed(() => {
207210
208211
const additionalAttributes = computed(() => {
209212
const additionalAttrs: Record<string, unknown> = {}
213+
const describedByIds: string[] = []
214+
210215
if (!!warningMessage || !!errorMessage || !!descriptionMessage) {
211-
additionalAttrs['aria-describedby'] = messageId
216+
describedByIds.push(messageId.value)
217+
}
218+
219+
if (type === 'password' && Object.keys(passwordPolicy).length) {
220+
describedByIds.push(`${id}-password-policy`)
212221
}
222+
223+
if (describedByIds.length > 0) {
224+
additionalAttrs['aria-describedby'] = describedByIds.join(' ')
225+
}
226+
213227
// FIXME: placeholder usage is discouraged, we need to find a better UX concept
214228
if (defaultValue) {
215229
additionalAttrs['placeholder'] = defaultValue

packages/design-system/src/components/OcTextInput/__snapshots__/OcTextInput.spec.ts.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ exports[`OcTextInput > password input field > password policy > displays error s
66
<!--v-if-->
77
<div class="oc-text-input-password-wrapper">
88
<!-- Input label is handled in the OcTextInput component -->
9-
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-24" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="password" value="pass"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
9+
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-24" aria-describedby="oc-textinput-24-password-policy" aria-invalid="true" class="oc-text-input oc-input oc-rounded" type="password" value="pass"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
1010
<!--v-if-->
1111
<!-- @slot Content of the button --> <span class="oc-icon oc-icon-s oc-icon-passive"><!----></span>
1212
</button> <button type="button" aria-label="Copy password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-copy-password-button oc-px-s oc-background-default">
@@ -16,7 +16,7 @@ exports[`OcTextInput > password input field > password policy > displays error s
1616
<!--v-if-->
1717
</div>
1818
<portal to="app.design-system.password-policy">
19-
<div class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s">
19+
<div id="oc-textinput-24-password-policy" class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s" role="status" aria-live="polite" aria-atomic="true"><span class="oc-invisible-sr">Password requirements: 8+ letters</span>
2020
<div class="oc-flex oc-flex-middle oc-text-input-password-policy-rule"><span class="oc-icon oc-icon-s oc-icon-danger oc-mr-xs"><!----></span> <span class="oc-text-input-danger">8+ letters</span>
2121
<!--v-if-->
2222
</div>
@@ -35,7 +35,7 @@ exports[`OcTextInput > password input field > password policy > displays success
3535
<!--v-if-->
3636
<div class="oc-text-input-password-wrapper">
3737
<!-- Input label is handled in the OcTextInput component -->
38-
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-25" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="password" value="password123"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
38+
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-25" aria-describedby="oc-textinput-25-password-policy" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="password" value="password123"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
3939
<!--v-if-->
4040
<!-- @slot Content of the button --> <span class="oc-icon oc-icon-s oc-icon-passive"><!----></span>
4141
</button> <button type="button" aria-label="Copy password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-copy-password-button oc-px-s oc-background-default">
@@ -45,7 +45,7 @@ exports[`OcTextInput > password input field > password policy > displays success
4545
<!--v-if-->
4646
</div>
4747
<portal to="app.design-system.password-policy">
48-
<div class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s">
48+
<div id="oc-textinput-25-password-policy" class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s" role="status" aria-live="polite" aria-atomic="true"><span class="oc-invisible-sr">Password meets all requirements</span>
4949
<div class="oc-flex oc-flex-middle oc-text-input-password-policy-rule"><span class="oc-icon oc-icon-s oc-icon-success oc-mr-xs"><!----></span> <span class="oc-text-input-success">8+ letters</span>
5050
<!--v-if-->
5151
</div>

packages/design-system/src/components/OcTextInputPassword/OcTextInputPassword.vue

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
:value="value"
1515
:type="showPassword ? 'text' : 'password'"
1616
:disabled="disabled"
17+
:aria-invalid="hasPasswordPolicyViolation"
1718
@input="$emit('input', $event)"
1819
@change="$emit('change', $event)"
1920
/>
@@ -52,7 +53,14 @@
5253
</oc-button>
5354
</div>
5455
<portal v-if="showPasswordPolicyInformation" to="app.design-system.password-policy">
55-
<div class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s">
56+
<div
57+
:id="passwordPolicyId"
58+
class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s"
59+
role="status"
60+
aria-live="polite"
61+
aria-atomic="true"
62+
>
63+
<span class="oc-invisible-sr" v-text="passwordPolicySummary" />
5664
<div
5765
v-for="(testedRule, index) in testedPasswordPolicy.rules"
5866
:key="index"
@@ -133,14 +141,44 @@ const showPassword = ref(false)
133141
const copyPasswordIconInitial = 'file-copy'
134142
const copyPasswordIcon = ref(copyPasswordIconInitial)
135143
144+
const inputId = computed(() => attrs.id as string)
145+
const passwordPolicyId = computed(() => `${inputId.value}-password-policy`)
146+
136147
const showPasswordPolicyInformation = computed(() => {
137-
return !!Object.keys(passwordPolicy.rules || {}).length
148+
return !!Object.keys(passwordPolicy?.rules || {}).length
138149
})
139150
140151
const testedPasswordPolicy = computed(() => {
141152
return passwordPolicy.missing(unref(value))
142153
})
143154
155+
const hasPasswordPolicyViolation = computed(() => {
156+
if (!passwordPolicy?.rules?.length) {
157+
return false
158+
}
159+
// Check if password has any policy violations or if there's an explicit error
160+
return !passwordPolicy.check(unref(value)) || hasError
161+
})
162+
163+
const passwordPolicySummary = computed(() => {
164+
const tested = testedPasswordPolicy.value
165+
if (!tested || !tested.rules || tested.rules.length === 0) {
166+
return ''
167+
}
168+
169+
const unmetRequirements = tested.rules.filter((rule) => !rule.verified)
170+
if (unmetRequirements.length === 0) {
171+
return $gettext('Password meets all requirements')
172+
}
173+
174+
const messages = unmetRequirements.map((rule) => getPasswordPolicyRuleMessage(rule))
175+
return $gettext(
176+
'Password requirements: %{requirements}',
177+
{ requirements: messages.join(', ') },
178+
true
179+
)
180+
})
181+
144182
const getPasswordPolicyRuleMessage = (rule: PasswordPolicyRule) => {
145183
const paramObj: Record<string, string> = {}
146184
@@ -170,7 +208,7 @@ const generatePassword = () => {
170208
watch(
171209
() => value,
172210
(value) => {
173-
if (!Object.keys(passwordPolicy).length) {
211+
if (!passwordPolicy?.rules?.length) {
174212
return
175213
}
176214

packages/web-app-files/src/components/SideBar/Shares/ExpirationDateIndicator.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<template>
22
<div class="oc-flex oc-flex-center expiration-date-indicator">
3-
<oc-icon v-oc-tooltip="expirationDateTooltip" name="calendar-event" fill-type="line" />
4-
<span class="oc-invisible-sr" v-text="screenreaderShareExpiration" />
3+
<oc-icon
4+
v-oc-tooltip="expirationDateTooltip"
5+
:accessible-label="screenreaderShareExpiration"
6+
name="calendar-event"
7+
fill-type="line"
8+
/>
59
</div>
610
</template>
711

packages/web-app-files/src/components/SideBar/Shares/Links/ListItem.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
name="lock-password"
3434
class="oc-files-file-link-has-password oc-mr-xs"
3535
fill-type="line"
36+
:accessible-label="$gettext('This link is password-protected')"
3637
:aria-label="$gettext('This link is password-protected')"
3738
role="img"
3839
/>

packages/web-pkg/src/components/CreateLinkModal.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
</div>
1212
<div class="link-modal-password oc-mb-m">
1313
<oc-text-input
14+
id="link-password-input"
1415
:key="passwordInputKey"
1516
:model-value="password.value"
1617
type="password"

packages/web-pkg/tests/unit/components/Filters/__snapshots__/DateFilter.spec.ts.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ exports[`DateFilter > can use a custom attribute as display name 1`] = `
5757
<!--v-if--> <input id="oc-textinput-7" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
5858
<!--v-if-->
5959
</div>
60-
<div class="oc-text-input-message">
60+
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
6161
<!--v-if--> <span id="oc-textinput-7-message" class=""></span>
6262
</div>
6363
<!--v-if-->
@@ -67,7 +67,7 @@ exports[`DateFilter > can use a custom attribute as display name 1`] = `
6767
<!--v-if--> <input id="oc-textinput-8" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
6868
<!--v-if-->
6969
</div>
70-
<div class="oc-text-input-message">
70+
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
7171
<!--v-if--> <span id="oc-textinput-8-message" class=""></span>
7272
</div>
7373
<!--v-if-->
@@ -142,7 +142,7 @@ exports[`DateFilter > renders all items 1`] = `
142142
<!--v-if--> <input id="oc-textinput-3" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
143143
<!--v-if-->
144144
</div>
145-
<div class="oc-text-input-message">
145+
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
146146
<!--v-if--> <span id="oc-textinput-3-message" class=""></span>
147147
</div>
148148
<!--v-if-->
@@ -152,7 +152,7 @@ exports[`DateFilter > renders all items 1`] = `
152152
<!--v-if--> <input id="oc-textinput-4" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
153153
<!--v-if-->
154154
</div>
155-
<div class="oc-text-input-message">
155+
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
156156
<!--v-if--> <span id="oc-textinput-4-message" class=""></span>
157157
</div>
158158
<!--v-if-->

0 commit comments

Comments
 (0)