A super tab idea

This commit is contained in:
Josh Perez
2023-05-04 15:34:52 -04:00
parent d9c0366219
commit db995addae
8 changed files with 152 additions and 86 deletions

View File

@@ -191,6 +191,7 @@ import { RetryPlaceholders } from './util/retryPlaceholders';
import { setBatchingStrategy } from './util/messageBatcher';
import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration';
import { makeLookup } from './util/makeLookup';
import { focusableSelectors } from './util/focusableSelectors';
export function isOverHourIntoPast(timestamp: number): boolean {
const HOUR = 1000 * 60 * 60;
@@ -1352,6 +1353,52 @@ export async function startApp(): Promise<void> {
return;
}
// Super tab :)
if (commandOrCtrl && key === 'F6') {
window.enterKeyboardMode();
const focusedElement = document.activeElement;
const targets: Array<HTMLElement> = Array.from(
document.querySelectorAll('[data-supertab="true"]')
);
const focusedIndex = targets.findIndex(target => {
if (!target || !focusedElement) {
return false;
}
if (target === focusedElement) {
return true;
}
if (target.contains(focusedElement)) {
return true;
}
return false;
});
const lastIndex = targets.length - 1;
const increment = shiftKey ? -1 : 1;
let index;
if (focusedIndex < 0 || focusedIndex >= lastIndex) {
index = 0;
} else {
index = focusedIndex + increment;
}
while (!targets[index]) {
index += increment;
if (index > lastIndex || index < 0) {
index = 0;
}
}
targets[index]
.querySelectorAll<HTMLElement>(focusableSelectors.join(','))[0]
?.focus();
}
// Navigate by section
if (commandOrCtrl && !shiftKey && (key === 't' || key === 'T')) {
window.enterKeyboardMode();

View File

@@ -96,76 +96,82 @@ export function CustomColorEditor({
includeAnotherBubble
/>
{selectedTab === TabViews.Gradient && (
<GradientDial
deg={color.deg}
knob1Style={{ backgroundColor: getHSL(color.start) }}
knob2Style={{
backgroundColor: getHSL(color.end || ULTRAMARINE_ISH_VALUES),
}}
onChange={deg => {
setColor({
...color,
deg,
});
}}
onClick={knob => setSelectedColorKnob(knob)}
selectedKnob={selectedColorKnob}
/>
<div data-supertab>
<GradientDial
deg={color.deg}
knob1Style={{ backgroundColor: getHSL(color.start) }}
knob2Style={{
backgroundColor: getHSL(
color.end || ULTRAMARINE_ISH_VALUES
),
}}
onChange={deg => {
setColor({
...color,
deg,
});
}}
onClick={knob => setSelectedColorKnob(knob)}
selectedKnob={selectedColorKnob}
/>
</div>
)}
</div>
<div className="CustomColorEditor__slider-container">
{i18n('icu:CustomColorEditor__hue')}
<Slider
handleStyle={{
backgroundColor: getHSL({
hue,
saturation: 100,
}),
}}
label={i18n('icu:CustomColorEditor__hue')}
moduleClassName="CustomColorEditor__hue-slider"
onChange={(percentage: number) => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
hue: getValue(percentage, MAX_HUE),
},
});
}}
value={getPercentage(hue, MAX_HUE)}
/>
<div data-supertab>
<div className="CustomColorEditor__slider-container">
{i18n('icu:CustomColorEditor__hue')}
<Slider
handleStyle={{
backgroundColor: getHSL({
hue,
saturation: 100,
}),
}}
label={i18n('icu:CustomColorEditor__hue')}
moduleClassName="CustomColorEditor__hue-slider"
onChange={(percentage: number) => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
hue: getValue(percentage, MAX_HUE),
},
});
}}
value={getPercentage(hue, MAX_HUE)}
/>
</div>
<div className="CustomColorEditor__slider-container">
{i18n('icu:CustomColorEditor__saturation')}
<Slider
containerStyle={getCustomColorStyle({
deg: 180,
start: { hue, saturation: 0 },
end: { hue, saturation: 100 },
})}
handleStyle={{
backgroundColor: getHSL(
color[selectedColorKnob] || ULTRAMARINE_ISH_VALUES
),
}}
label={i18n('icu:CustomColorEditor__saturation')}
moduleClassName="CustomColorEditor__saturation-slider"
onChange={(value: number) => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
saturation: value,
},
});
}}
value={saturation}
/>
</div>
</div>
<div className="CustomColorEditor__slider-container">
{i18n('icu:CustomColorEditor__saturation')}
<Slider
containerStyle={getCustomColorStyle({
deg: 180,
start: { hue, saturation: 0 },
end: { hue, saturation: 100 },
})}
handleStyle={{
backgroundColor: getHSL(
color[selectedColorKnob] || ULTRAMARINE_ISH_VALUES
),
}}
label={i18n('icu:CustomColorEditor__saturation')}
moduleClassName="CustomColorEditor__saturation-slider"
onChange={(value: number) => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
saturation: value,
},
});
}}
value={saturation}
/>
</div>
<div className="CustomColorEditor__footer">
<div className="CustomColorEditor__footer" data-supertab>
<Button variant={ButtonVariant.Secondary} onClick={onClose}>
{i18n('icu:cancel')}
</Button>

View File

@@ -3,7 +3,6 @@
import type { AudioDevice } from '@signalapp/ringrtc';
import type { ReactNode } from 'react';
import focusableSelectors from 'focusable-selectors';
import React, {
useCallback,
useEffect,
@@ -59,6 +58,7 @@ import { DurationInSeconds } from '../util/durations';
import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useUniqueId } from '../hooks/useUniqueId';
import { useTheme } from '../hooks/useTheme';
import { focusableSelectors } from '../util/focusableSelectors';
type CheckboxChangeHandlerType = (value: boolean) => unknown;
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;

View File

@@ -63,7 +63,7 @@ export function useTabs(options: TabsOptionsType): TabsProps {
}
const tabsHeaderElement = (
<div className={getClassName('')}>
<div className={getClassName('')} data-supertab>
{options.tabs.map(({ id, label }) => (
<div
className={classNames(

View File

@@ -0,0 +1,30 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// https://www.npmjs.com/package/focusable-selectors
// https://github.com/KittyGiraudel/focusable-selectors/issues/15
const not = {
inert: ':not([inert]):not([inert] *)',
negTabIndex: ':not([tabindex^="-"])',
disabled: ':not(:disabled)',
};
export const focusableSelectors = [
`a[href]${not.inert}${not.negTabIndex}`,
`area[href]${not.inert}${not.negTabIndex}`,
`input:not([type="hidden"]):not([type="radio"])${not.inert}${not.negTabIndex}${not.disabled}`,
`input[type="radio"]${not.inert}${not.negTabIndex}${not.disabled}`,
`select${not.inert}${not.negTabIndex}${not.disabled}`,
`textarea${not.inert}${not.negTabIndex}${not.disabled}`,
`button${not.inert}${not.negTabIndex}${not.disabled}`,
`details${not.inert} > summary:first-of-type${not.negTabIndex}`,
// Discard until Firefox supports `:has()`
// See: https://github.com/KittyGiraudel/focusable-selectors/issues/12
// `details:not(:has(> summary))${not.inert}${not.negTabIndex}`,
`iframe${not.inert}${not.negTabIndex}`,
`audio[controls]${not.inert}${not.negTabIndex}`,
`video[controls]${not.inert}${not.negTabIndex}`,
`[contenteditable]${not.inert}${not.negTabIndex}`,
`[tabindex]${not.inert}${not.negTabIndex}`,
];