Skip to content

Commit

Permalink
feat(NcButton): Add pressed state for stateful buttons, like the `N…
Browse files Browse the repository at this point in the history
…cAppSidebar` star button

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed Jul 18, 2023
1 parent 6c53f77 commit b4b50a0
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 2 deletions.
9 changes: 8 additions & 1 deletion src/components/NcAppSidebar/NcAppSidebar.vue
Expand Up @@ -32,6 +32,7 @@ include a standard-header like it's used by the files app.
```vue
<template>
<NcAppSidebar
:starred="starred"
name="cat-picture.jpg"
subname="last edited 3 weeks ago">
<NcAppSidebarTab name="Search" id="search-tab">
Expand Down Expand Up @@ -65,6 +66,11 @@ include a standard-header like it's used by the files app.
Cog,
ShareVariant,
},
data() {
return {
starred: false,
}
},
}
</script>
```
Expand Down Expand Up @@ -376,6 +382,7 @@ export default {
<slot name="tertiary-actions">
<NcButton v-if="canStar"
:aria-label="favoriteTranslated"
:pressed="isStarred"
class="app-sidebar-header__star"
type="secondary"
@click.prevent="toggleStarred">
Expand Down Expand Up @@ -974,7 +981,7 @@ $top-buttons-spacing: 6px;
.app-sidebar-header__star {
// Override default Button component styles
box-shadow: none;
&:hover {
&:not([aria-pressed='true']):hover {
box-shadow: none;
background-color: var(--color-background-hover);
}
Expand Down
99 changes: 98 additions & 1 deletion src/components/NcButton/NcButton.vue
Expand Up @@ -196,6 +196,66 @@ button {
}
</style>
```

### Pressed state
It is possible to make the button stateful by adding a pressed state, e.g. if you like to create a favorite button.
The button will have the required `aria` attribute for accessibility and visual style (`primary` when pressed, and the configured type otherwise).

```vue
<template>
<div>
<div style="display: flex; gap: 12px;">
<NcButton :pressed.sync="isFavorite" :aria-label="ariaLabel" type="tertiary-no-background">
<template #icon>
<IconStar v-if="isFavorite" :size="20" />
<IconStarOutline v-else :size="20" />
</template>
</NcButton>
<NcButton :pressed.sync="isFavorite" :aria-label="ariaLabel" type="tertiary">
<template #icon>
<IconStar v-if="isFavorite" :size="20" />
<IconStarOutline v-else :size="20" />
</template>
</NcButton>
<NcButton :pressed.sync="isFavorite" :aria-label="ariaLabel">
<template #icon>
<IconStar v-if="isFavorite" :size="20" />
<IconStarOutline v-else :size="20" />
</template>
</NcButton>
</div>
<div>
It is {{ isFavorite ? 'a' : 'not a' }} favorite.
</div>
</div>
</template>
<script>
import IconStar from 'vue-material-design-icons/Star.vue'
import IconStarOutline from 'vue-material-design-icons/StarOutline.vue'
export default {
components: {
IconStar,
IconStarOutline,
},
data() {
return {
isFavorite: false,
}
},
computed: {
ariaLabel() {
return this.isFavorite ? 'Remove as favorite' : 'Add as favorite'
},
},
methods: {
toggleFavorite() {
this.isFavorite = !this.isFavorite
},
},
}
</script>
```
</docs>

<script>
Expand Down Expand Up @@ -299,6 +359,33 @@ export default {
type: Boolean,
default: null,
},
/**
* The pressed state of the button if it has a checked state
* This will add the `aria-pressed` attribute and for the button to have the primary style in checked state.
*/
pressed: {
type: Boolean,
default: null,
},
},
computed: {
/**
* The real type to be used for the button, enforces `primary` for pressed state and, if stateful button, any other type for not pressed state
* Otherwise the type property is used.
*/
realType() {
// Force *primary* when pressed
if (this.pressed) {
return 'primary'
}
// If not pressed but button is configured as stateful button then the type must not be primary
if (this.pressed === false && this.type === 'primary') {
return 'secondary'
}
return this.type
},
},
/**
Expand Down Expand Up @@ -332,14 +419,15 @@ export default {
'button-vue--icon-only': hasIcon && !hasText,
'button-vue--text-only': hasText && !hasIcon,
'button-vue--icon-and-text': hasIcon && hasText,
[`button-vue--vue-${this.type}`]: this.type,
[`button-vue--vue-${this.realType}`]: this.realType,
'button-vue--wide': this.wide,
active: isActive,
'router-link-exact-active': isExactActive,
},
],
attrs: {
'aria-label': this.ariaLabel,
'aria-pressed': this.pressed,
disabled: this.disabled,
type: this.href ? null : this.nativeType,
role: this.href ? 'button' : null,
Expand All @@ -352,6 +440,15 @@ export default {
on: {
...this.$listeners,
click: ($event) => {
// Update pressed prop on click if it is set
if (typeof this.$props.pressed === 'boolean') {
/**
* Update the current pressed state of the button (if the `pressed` property was configured)
*
* @property {boolean} newValue The new `pressed`-state
*/
this.$emit('update:pressed', !this.$props.pressed)
}
// We have to both navigate and call the listeners click handler
this.$listeners?.click?.($event)
navigate?.($event)
Expand Down

0 comments on commit b4b50a0

Please sign in to comment.