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 + + +``` + +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 + + +``` + +It is also possible to use the button with radio semantics, this is only possible in menus and not for inline actions! + +```vue + + +``` @@ -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 })