Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

Introduce search:input event to select, deprecate autocomplete and add OcRecipient #1521

Merged
merged 9 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 2 additions & 1 deletion .stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"selector-max-compound-selectors": null,
"selector-max-id": null,
"selector-no-qualifying-type": null,
"property-no-vendor-prefix": null
"property-no-vendor-prefix": null,
"string-quotes": "double"
}
}
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-deprecate-autocomplete
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: deprecate OcAutocomplete

We've deprecated the OcAutocomplete component. OcSelect with `:multiple="true"` prop can be used to achieve the same behaviour.

https://github.com/owncloud/owncloud-design-system/pull/1521
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-oc-recipient
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: add OcRecipient component

We've added OcRecipient component.

https://github.com/owncloud/owncloud-design-system/pull/1521
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-select-search-input
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: add `search:input` event to OcSelect

We've added `search:input` event to the OcSelect component which is triggered when a search query is typed into the select.

https://github.com/owncloud/owncloud-design-system/pull/1521
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"focus-trap": "^6.4.0",
"focus-trap-vue": "^1.1.1",
"friendly-errors-webpack-plugin": "^1.7.0",
"fuse.js": "^6.4.6",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.0",
"jest": "^26.6.3",
Expand Down Expand Up @@ -144,6 +145,7 @@
"filesize": "^7.0.0",
"focus-trap": "^6.4.0",
"focus-trap-vue": "^1.1.1",
"fuse.js": "^6.4.6",
"luxon": "^1.22.0",
"postcss-import": "^12.0.1",
"postcss-url": "^9.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/assets/icons/user_remote.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/OcAutocomplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ import uniqueId from "../utils/uniqueId"
export default {
name: "OcAutocomplete",
components: { OcSpinner },
status: "ready",
status: "deprecated",
release: "1.0.0",
props: {
/**
Expand Down
10 changes: 5 additions & 5 deletions src/components/OcRadio.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ describe("OcRadio", () => {
})
})
describe("radio input", () => {
it("should emit input event if clicked", async () => {
it("should emit input event if checked", async () => {
const wrapper = getWrapper()
const radioInput = wrapper.find(radioElementSelector)
expect(wrapper.emitted("input")).toBeFalsy()
await radioInput.trigger("click")
expect(radioInput.element.checked).toBeTruthy()
await radioInput.setChecked()
expect(radioInput.element.selected).toBeTruthy()
expect(wrapper.emitted("input")).toBeTruthy()
})
it("should not emit input event if disabled", async () => {
Expand Down Expand Up @@ -113,11 +113,11 @@ describe("OcRadio", () => {
it("should set aria-checked if option equals selected value", async () => {
const wrapper = mount(Component, options)
const inputs = wrapper.findAll(radioElementSelector)
await inputs.at(0).trigger("click")
await inputs.at(0).setChecked()
expect(inputs.at(0).attributes("aria-checked")).toBe("true")
expect(inputs.at(1).attributes("aria-checked")).toBe("false")
expect(inputs.at(2).attributes("aria-checked")).toBe("false")
await inputs.at(1).trigger("click")
await inputs.at(1).setChecked()
expect(inputs.at(0).attributes("aria-checked")).toBe("false")
expect(inputs.at(1).attributes("aria-checked")).toBe("true")
expect(inputs.at(2).attributes("aria-checked")).toBe("false")
Expand Down
89 changes: 89 additions & 0 deletions src/components/OcRecipient/OcRecipient.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { shallowMount } from "@vue/test-utils"

import Recipient from "./OcRecipient.vue"

describe("OcRecipient", () => {
/**
* @param {Object} props
* @returns {Wrapper<Vue>}
*/
function getWrapper(props, slot) {
const slots = slot ? { append: slot } : {}

return shallowMount(Recipient, {
propsData: {
recipient: {
name: "alice",
avatar: "avatar.jpg",
hasAvatar: true,
isLoadingAvatar: false,
...props,
},
},
slots,
})
}

it("displays recipient name", () => {
const wrapper = getWrapper()

expect(wrapper.find('[data-testid="recipient-name"]').text()).toEqual("alice")
})

it("displays avatar", () => {
const wrapper = getWrapper()

expect(wrapper.find('[data-testid="recipient-avatar"]').attributes("src")).toEqual("avatar.jpg")
})

it("displays a spinner if avatar has not been loaded yet", () => {
const wrapper = getWrapper({
isLoadingAvatar: true,
})

expect(wrapper.find('[data-testid="recipient-avatar-spinner"]').exists()).toBeTruthy()
})

it("displays an icon if avatar is not enabled", () => {
const wrapper = getWrapper({
icon: {
name: "person",
label: "User",
},
hasAvatar: false,
})

const icon = wrapper.find('[data-testid="recipient-icon"]')

expect(icon.exists()).toBeTruthy()
expect(icon.attributes().accessiblelabel).toEqual("User")
})

it("display content in the append slot", () => {
const wrapper = getWrapper({}, '<span id="test-slot">Hello world</span>')

expect(wrapper.find("#test-slot").exists()).toBeTruthy()
})

it.each([
["name is not defined", {}],
[
"name is not a string",
{
name: {
first: "Alice",
},
},
],
[
"name is empty",
{
name: "",
},
],
])("throws an error if %s", (def, prop) => {
expect(() => shallowMount(Recipient, { propsData: { recipient: prop } })).toThrow(
`Recipient ${def}`
)
})
})
105 changes: 105 additions & 0 deletions src/components/OcRecipient/OcRecipient.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<template>
<span class="oc-recipient">
<template v-if="recipient.hasAvatar">
<oc-spinner
v-if="recipient.isLoadingAvatar"
key="recipient-avatar-spinner"
size="small"
:aria-label="$gettext('Loading avatar')"
data-testid="recipient-avatar-spinner"
/>
<oc-avatar
v-else
:key="recipient.avatar || recipient.name"
data-testid="recipient-avatar"
class="oc-recipient-avatar"
:src="recipient.avatar"
:user-name="recipient.name"
:width="16.8"
/>
</template>
<oc-icon
v-else
class="oc-recipient-icon"
size="small"
:name="recipient.icon.name"
:accessible-label="recipient.icon.label"
data-testid="recipient-icon"
/>
<p class="oc-recipient-name" data-testid="recipient-name" v-text="recipient.name" />
<!-- @slot Append content (actions, additional info, etc.) -->
<slot name="append" />
</span>
</template>

<script>
import OcAvatar from "../avatars/OcAvatar.vue"
import OcIcon from "../OcIcon.vue"
import OcSpinner from "../OcSpinner.vue"

export default {
name: "OcRecipient",
status: "review",
release: "unreleased",

components: { OcAvatar, OcIcon, OcSpinner },

props: {
/**
* Recipient object containing name (required), icon and avatar
*/
recipient: {
type: Object,
required: true,
validator: recipient => {
if (!Object.prototype.hasOwnProperty.call(recipient, "name")) {
throw new Error("Recipient name is not defined")
}

if (typeof recipient.name !== "string") {
throw new Error("Recipient name is not a string")
}

if (recipient.name.length < 1) {
throw new Error("Recipient name is empty")
}

LukasHirt marked this conversation as resolved.
Show resolved Hide resolved
return true
},
},
},
}
</script>

<style lang="scss">
.oc-recipient {
align-items: center;
background-color: var(--oc-color-swatch-inverse-default);
border: 1px solid var(--oc-color-input-border);
border-radius: 6px;
display: flex;
gap: var(--oc-space-xsmall);
justify-content: flex-start;
padding: var(--oc-space-xsmall);
width: auto;

&-icon > svg {
fill: var(--oc-color-text-default);
}

&-name {
color: var(--oc-color-text-default);
margin: 0;
padding: 0;
}
}
</style>

<docs>
```js
<div class="uk-flex uk-flex-column uk-flex-left uk-flex-top">
<oc-recipient :recipient="{ name: 'Alice', avatar: 'https://picsum.photos/24', hasAvatar: true, isAvatarLoading: false }" class="oc-mb-s" />
<oc-recipient :recipient="{ name: 'Alice', icon: { name: 'person', label: 'User' }, hasAvatar: false }" />
</div>
```
</docs>
Loading