diff --git a/src/assets/action.scss b/src/assets/action.scss
index 67b75a0228..65a9e60028 100644
--- a/src/assets/action.scss
+++ b/src/assets/action.scss
@@ -22,7 +22,7 @@
*/
@mixin action-active {
- li {
+ li.action {
&.active {
background-color: var(--color-background-hover);
border-radius: 6px;
diff --git a/src/components/NcActionButton/NcActionButton.vue b/src/components/NcActionButton/NcActionButton.vue
index 5e3fbe0847..1c71165392 100644
--- a/src/components/NcActionButton/NcActionButton.vue
+++ b/src/components/NcActionButton/NcActionButton.vue
@@ -170,17 +170,127 @@ export default {
}
```
+
+### With different model behavior
+By default the button will act like a normal button, but it is also possible to change the behavior to a toggle button, checkbox button or radio button.
+
+For example to have the button act like a toggle button just set the `modelValue` property to the toggle state:
+
+```vue
+
+
+
+
+
+
+ Fullscreen
+
+
+
+
+```
+
+Another example would be using it with checkbox semantics, to enable or disable features.
+This also allows tri-state behavior (`true`, `false`, `null`) in which case `aria-checked` will be either `true`, `false` or `mixed`.
+
+```vue
+
+
+
+
+
+
+ Raise hand
+
+
+
+
+
+ Fullscreen
+
+
+
+
+```
+
+It is also possible to use the button with radio semantics, this is only possible in menus and not for inline actions!
+
+```vue
+
+
+
+
+
+
+ Pay with cash
+
+
+
+
+
+ Pay by card
+
+
+
+
+```
-
@@ -289,4 +503,9 @@ export default {
@include action-active;
@include action--disabled;
@include action-item('button');
+
+.action-button__pressed-icon {
+ margin-left: auto;
+ margin-right: -$icon-margin;
+}
diff --git a/src/components/NcActionButtonGroup/NcActionButtonGroup.vue b/src/components/NcActionButtonGroup/NcActionButtonGroup.vue
index 1c6bc4e945..e840ce1461 100644
--- a/src/components/NcActionButtonGroup/NcActionButtonGroup.vue
+++ b/src/components/NcActionButtonGroup/NcActionButtonGroup.vue
@@ -29,19 +29,25 @@ This should be used sparingly for accessibility.
+ :model-value.sync="alignment"
+ type="radio"
+ value="l">
+ :model-value.sync="alignment"
+ type="radio"
+ value="c">
+ :model-value.sync="alignment"
+ type="radio"
+ value="r">
@@ -70,6 +76,9 @@ export default {
AlignCenter,
Plus,
},
+ data() {
+ return { alignment: 'l' }
+ },
methods: {
showMessage(msg) {
alert(msg)
@@ -141,6 +150,7 @@ export default defineComponent({
ul.nc-button-group-content {
display: flex;
+ gap: 4px; // required for the focus-visible outline
justify-content: space-between;
li {
flex: 1 1;
@@ -152,6 +162,20 @@ export default defineComponent({
width: 100%;
display: flex;
justify-content: center;
+
+ &.action-button--active {
+ background-color: var(--color-primary-element);
+ border-radius: var(--border-radius-large);
+ color: var(--color-primary-element-text);
+
+ &:hover, &:focus, &:focus-within {
+ background-color: var(--color-primary-element-hover);
+ }
+ }
+
+ .action-button__pressed-icon {
+ display: none;
+ }
}
}
}
diff --git a/src/components/NcActions/NcActions.vue b/src/components/NcActions/NcActions.vue
index 4610f61460..fe36aa8694 100644
--- a/src/components/NcActions/NcActions.vue
+++ b/src/components/NcActions/NcActions.vue
@@ -1303,6 +1303,12 @@ export default {
title = text
}
+ const propsToForward = { ...(action?.componentOptions?.propsData ?? {}) }
+ const nativeType = ['submit', 'reset'].includes(propsToForward.type) ? propsToForward.modelValue : 'button'
+ // not available on NcButton or with different meaning
+ delete propsToForward.modelValue
+ delete propsToForward.type
+
return h('NcButton',
{
class: [
@@ -1320,11 +1326,15 @@ export default {
// If it has a menuName, we use a secondary button
type: this.type || (buttonText ? 'secondary' : 'tertiary'),
disabled: this.disabled || action?.componentOptions?.propsData?.disabled,
- ...action?.componentOptions?.propsData,
+ pressed: action?.componentOptions?.propsData?.modelValue,
+ nativeType,
+ ...propsToForward,
},
on: {
focus: this.onFocus,
blur: this.onBlur,
+ // forward any pressed state from NcButton just like NcActionButton does
+ 'update:pressed': action?.componentOptions?.listeners?.['update:modelValue'] ?? (() => {}),
// If we have a click listener,
// we bind it to execute on click and forward the click event
...(!!clickListener && {
diff --git a/tests/unit/components/NcActions/NcActions.spec.ts b/tests/unit/components/NcActions/NcActions.spec.ts
index 39048332fc..a098e063be 100644
--- a/tests/unit/components/NcActions/NcActions.spec.ts
+++ b/tests/unit/components/NcActions/NcActions.spec.ts
@@ -139,17 +139,16 @@ describe('NcActions.vue', () => {
})
})
it('shows the first action outside.', () => {
- expect(wrapper.findAll('.action-item').length).toBe(2)
expect(wrapper.findAll('button.action-item').length).toBe(1)
- expect(wrapper.find('button.action-item').exists()).toBe(true)
+ expect(wrapper.find('button.action-item[aria-label="Test1"]').exists()).toBe(true)
})
it('shows the menu toggle.', () => {
expect(wrapper.find('.action-item__menutoggle').exists()).toBe(true)
})
it('shows the first two action outside on prop change.', async () => {
await wrapper.setProps({ inline: 2 })
- expect(wrapper.findAll('.action-item').length).toBe(3)
expect(wrapper.findAll('button.action-item').length).toBe(2)
+ expect(wrapper.find('.action-item__menutoggle').exists()).toBe(true)
})
it('shows all actions outside on prop change.', async () => {
await wrapper.setProps({ inline: 3 })