diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css index fcb7b96e50df..1bcc07735dda 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css @@ -25,9 +25,25 @@ transition: background 0.2s ease-in; } +.bubblePadding.sideNavGrid { + padding-block: 5px; + padding-inline: 0; + gap: 10px; + block-size: unset; +} + +.bubblePadding.sideNavGrid > .bubble { + inline-size: 35px; + block-size: 35px; +} + +.bubblePadding.sideNavGrid > .channelName { + max-inline-size: 90%; + font-size: 10.5px; + white-space: nowrap; +} + .bubble { - inline-size: 50px; - block-size: 50px; border-radius: 100%; -webkit-border-radius: 100%; } diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js index c9854b34d6eb..a8632c5c70bc 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js @@ -15,10 +15,22 @@ export default defineComponent({ type: String, required: true }, + bubbleSize: { + type: Number, + default: 50 + }, showSelected: { type: Boolean, default: false - } + }, + showTitle: { + type: Boolean, + default: true + }, + sideNavGrid: { + type: Boolean, + default: false + }, }, data: function () { return { diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue index 53abe2f282c3..befe46a8e556 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue @@ -2,15 +2,24 @@
@@ -23,11 +32,16 @@ role="button" tabindex="0" :aria-labelledby="sanitizedId" + :title="showTitle ? null : channelName" @click="handleClick" @keydown.space.enter.prevent="handleClick($event)" > @@ -41,6 +55,7 @@ />
diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.js b/src/renderer/components/ft-icon-button/ft-icon-button.js index 93c4db4df142..f523ceea46ce 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.js +++ b/src/renderer/components/ft-icon-button/ft-icon-button.js @@ -1,10 +1,12 @@ import { defineComponent } from 'vue' import FtPrompt from '../ft-prompt/ft-prompt.vue' +import FtButton from '../ft-button/ft-button.vue' import { sanitizeForHtmlId } from '../../helpers/accessibility' export default defineComponent({ name: 'FtIconButton', components: { + 'ft-button': FtButton, 'ft-prompt': FtPrompt }, props: { @@ -50,7 +52,9 @@ export default defineComponent({ }, dropdownOptions: { // Array of objects with these properties - // - type: ('labelValue'|'divider', default to 'labelValue' for less typing) + // - type: ('labelValue'|'divider'|'radiogroup', default to 'labelValue' for less typing) + // - radios: ({ type?: 'radio', label: String, value: String, checked: Boolean } | { type: divider })[] + // (if type == 'radiogroup', representing a separate group of checkboxes) // - label: String (if type == 'labelValue') // - value: String (if type == 'labelValue') type: Array, @@ -59,15 +63,31 @@ export default defineComponent({ dropdownModalOnMobile: { type: Boolean, default: false + }, + closeOnClick: { + type: Boolean, + default: true + }, + hideIcon: { + type: Boolean, + default: false + }, + useFtButton: { + type: Boolean, + default: false } }, data: function () { return { dropdownShown: false, - mouseDownOnIcon: false, useModal: false } }, + computed: { + id: function () { + return sanitizeForHtmlId(`iconButton-${this.title}`) + } + }, mounted: function () { if (this.dropdownModalOnMobile) { this.useModal = window.innerWidth <= 900 @@ -102,16 +122,8 @@ export default defineComponent({ } }, - handleIconMouseDown: function () { - if (this.dropdownShown) { - this.mouseDownOnIcon = true - } - }, - handleDropdownFocusOut: function () { - if (this.mouseDownOnIcon) { - this.mouseDownOnIcon = false - } else if (!this.$refs.dropdown.matches(':focus-within')) { + if (this.dropdownShown && !this.$refs.iconButton.matches(':focus-within')) { this.dropdownShown = false } }, @@ -123,7 +135,58 @@ export default defineComponent({ this.$emit('click', url) } - this.dropdownShown = false + if (this.closeOnClick) { + this.dropdownShown = false + } + }, + + handleRadioDropdownKeydown: function({ url, index, groupIndex }, event) { + if (!(event instanceof KeyboardEvent) || event.altKey) { + return + } + + let isNext = null + switch (event.key) { + case ' ': + case 'Spacebar': + this.handleDropdownClick({ url, index }) + return + case 'ArrowRight': + case 'ArrowDown': + isNext = true + break + case 'ArrowLeft': + case 'ArrowUp': + isNext = false + break + default: + return + } + + this.focusElement( + { + isNext, + index, + list: this.dropdownOptions[groupIndex].radios, + idPrefix: this.title, + groupIndex + } + ) + }, + + focusElement: function ({ isNext, index, list, idPrefix, groupIndex }) { + let newIndex = index + const max = list.length - 1 + do { + newIndex += (isNext ? 1 : -1) + if (newIndex === -1) { + newIndex = max + } else if (newIndex > max) { + newIndex = 0 + } + } while (list[newIndex].type === 'divider') + const newElement = document.getElementById(sanitizeForHtmlId(idPrefix + groupIndex + '-' + newIndex)) + newElement?.focus() }, handleResize: function () { diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.scss b/src/renderer/components/ft-icon-button/ft-icon-button.scss index 58f776dd6414..7f083e0c75a8 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.scss +++ b/src/renderer/components/ft-icon-button/ft-icon-button.scss @@ -81,6 +81,12 @@ } } +.iconFtButton { + display: flex; + gap: 10px; + inline-size: 100%; +} + .iconDropdown { background-color: var(--side-nav-color); box-shadow: 0 1px 2px rgb(0 0 0 / 50%); @@ -110,13 +116,13 @@ inset-block-start: 45px; } - .list { + .list, .radiogroup { list-style-type: none; margin: 0; padding: 0; } - .listItem { + .listItem, .radioItem { cursor: pointer; margin: 0; padding-block: 8px; @@ -138,6 +144,18 @@ } } + .radioItem { + padding-inline: 10px; + display: flex; + gap: 15px; + text-align: start; + justify-content: space-between; + } + + .uncheckedFiller { + inline-size: 10.5px; + } + .listItemDivider { border-block-start: 1px solid var(--tertiary-text-color); margin-block: 1px; diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.vue b/src/renderer/components/ft-icon-button/ft-icon-button.vue index b1760e528e60..9d8059fe7cf6 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.vue +++ b/src/renderer/components/ft-icon-button/ft-icon-button.vue @@ -1,6 +1,12 @@