feat: Add quick settings menu with theme switcher and other changes (#673)
Co-authored-by: Mo Bitar <mo@standardnotes.org> Co-authored-by: Antonella Sgarlatta <antsgar@gmail.com>
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M10 18C8.94943 18 7.90914 17.7931 6.93853 17.391C5.96793 16.989 5.08601 16.3997 4.34315 15.6569C2.84285 14.1566 2 12.1217 2 10C2 7.87827 2.84285 5.84344 4.34315 4.34315C5.84344 2.84285 7.87827 2 10 2C14.4 2 18 5.2 18 9.2C18 10.473 17.4943 11.6939 16.5941 12.5941C15.6939 13.4943 14.473 14 13.2 14H11.76C11.52 14 11.36 14.16 11.36 14.4C11.36 14.48 11.44 14.56 11.44 14.64C11.76 15.04 11.92 15.52 11.92 16C12 17.12 11.12 18 10 18ZM10 3.6C8.30261 3.6 6.67475 4.27428 5.47452 5.47452C4.27428 6.67475 3.6 8.30261 3.6 10C3.6 11.6974 4.27428 13.3253 5.47452 14.5255C6.67475 15.7257 8.30261 16.4 10 16.4C10.24 16.4 10.4 16.24 10.4 16C10.4 15.84 10.32 15.76 10.32 15.68C10 15.28 9.84 14.88 9.84 14.4C9.84 13.28 10.72 12.4 11.84 12.4H13.2C14.0487 12.4 14.8626 12.0629 15.4627 11.4627C16.0629 10.8626 16.4 10.0487 16.4 9.2C16.4 6.08 13.52 3.6 10 3.6ZM5.6 8.4C6.24 8.4 6.8 8.96 6.8 9.6C6.8 10.24 6.24 10.8 5.6 10.8C4.96 10.8 4.4 10.24 4.4 9.6C4.4 8.96 4.96 8.4 5.6 8.4ZM8 5.2C8.64 5.2 9.2 5.76 9.2 6.4C9.2 7.04 8.64 7.6 8 7.6C7.36 7.6 6.8 7.04 6.8 6.4C6.8 5.76 7.36 5.2 8 5.2ZM12 5.2C12.64 5.2 13.2 5.76 13.2 6.4C13.2 7.04 12.64 7.6 12 7.6C11.36 7.6 10.8 7.04 10.8 6.4C10.8 5.76 11.36 5.2 12 5.2ZM14.4 8.4C15.04 8.4 15.6 8.96 15.6 9.6C15.6 10.24 15.04 10.8 14.4 10.8C13.76 10.8 13.2 10.24 13.2 9.6C13.2 8.96 13.76 8.4 14.4 8.4Z" fill="#72767E"/>
|
<path d="M10 18C8.94943 18 7.90914 17.7931 6.93853 17.391C5.96793 16.989 5.08601 16.3997 4.34315 15.6569C2.84285 14.1566 2 12.1217 2 10C2 7.87827 2.84285 5.84344 4.34315 4.34315C5.84344 2.84285 7.87827 2 10 2C14.4 2 18 5.2 18 9.2C18 10.473 17.4943 11.6939 16.5941 12.5941C15.6939 13.4943 14.473 14 13.2 14H11.76C11.52 14 11.36 14.16 11.36 14.4C11.36 14.48 11.44 14.56 11.44 14.64C11.76 15.04 11.92 15.52 11.92 16C12 17.12 11.12 18 10 18ZM10 3.6C8.30261 3.6 6.67475 4.27428 5.47452 5.47452C4.27428 6.67475 3.6 8.30261 3.6 10C3.6 11.6974 4.27428 13.3253 5.47452 14.5255C6.67475 15.7257 8.30261 16.4 10 16.4C10.24 16.4 10.4 16.24 10.4 16C10.4 15.84 10.32 15.76 10.32 15.68C10 15.28 9.84 14.88 9.84 14.4C9.84 13.28 10.72 12.4 11.84 12.4H13.2C14.0487 12.4 14.8626 12.0629 15.4627 11.4627C16.0629 10.8626 16.4 10.0487 16.4 9.2C16.4 6.08 13.52 3.6 10 3.6ZM5.6 8.4C6.24 8.4 6.8 8.96 6.8 9.6C6.8 10.24 6.24 10.8 5.6 10.8C4.96 10.8 4.4 10.24 4.4 9.6C4.4 8.96 4.96 8.4 5.6 8.4ZM8 5.2C8.64 5.2 9.2 5.76 9.2 6.4C9.2 7.04 8.64 7.6 8 7.6C7.36 7.6 6.8 7.04 6.8 6.4C6.8 5.76 7.36 5.2 8 5.2ZM12 5.2C12.64 5.2 13.2 5.76 13.2 6.4C13.2 7.04 12.64 7.6 12 7.6C11.36 7.6 10.8 7.04 10.8 6.4C10.8 5.76 11.36 5.2 12 5.2ZM14.4 8.4C15.04 8.4 15.6 8.96 15.6 9.6C15.6 10.24 15.04 10.8 14.4 10.8C13.76 10.8 13.2 10.24 13.2 9.6C13.2 8.96 13.76 8.4 14.4 8.4Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -64,6 +64,7 @@ import { IconDirective } from './components/Icon';
|
|||||||
import { NoteTagsContainerDirective } from './components/NoteTagsContainer';
|
import { NoteTagsContainerDirective } from './components/NoteTagsContainer';
|
||||||
import { PreferencesDirective } from './preferences';
|
import { PreferencesDirective } from './preferences';
|
||||||
import { AppVersion, IsWebPlatform } from '@/version';
|
import { AppVersion, IsWebPlatform } from '@/version';
|
||||||
|
import { QuickSettingsMenuDirective } from './components/QuickSettingsMenu';
|
||||||
|
|
||||||
function reloadHiddenFirefoxTab(): boolean {
|
function reloadHiddenFirefoxTab(): boolean {
|
||||||
/**
|
/**
|
||||||
@@ -154,6 +155,7 @@ const startApplication: StartApplication = async function startApplication(
|
|||||||
.directive('syncResolutionMenu', () => new SyncResolutionMenu())
|
.directive('syncResolutionMenu', () => new SyncResolutionMenu())
|
||||||
.directive('sessionsModal', SessionsModalDirective)
|
.directive('sessionsModal', SessionsModalDirective)
|
||||||
.directive('accountMenu', AccountMenuDirective)
|
.directive('accountMenu', AccountMenuDirective)
|
||||||
|
.directive('quickSettingsMenu', QuickSettingsMenuDirective)
|
||||||
.directive('noAccountWarning', NoAccountWarningDirective)
|
.directive('noAccountWarning', NoAccountWarningDirective)
|
||||||
.directive('protectedNotePanel', NoProtectionsdNoteWarningDirective)
|
.directive('protectedNotePanel', NoProtectionsdNoteWarningDirective)
|
||||||
.directive('searchOptions', SearchOptionsDirective)
|
.directive('searchOptions', SearchOptionsDirective)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const AdvancedOptions: FunctionComponent<Props> = observer(
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="sn-dropdown-item font-bold"
|
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none font-bold"
|
||||||
onClick={toggleShowAdvanced}
|
onClick={toggleShowAdvanced}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { WebApplication } from '@/ui_models/application';
|
|||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { StateUpdater, useRef, useState } from 'preact/hooks';
|
import { StateUpdater, useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { AccountMenuPane } from '.';
|
import { AccountMenuPane } from '.';
|
||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
import { Checkbox } from '../Checkbox';
|
import { Checkbox } from '../Checkbox';
|
||||||
@@ -31,6 +31,10 @@ export const ConfirmPassword: FunctionComponent<Props> = observer(
|
|||||||
|
|
||||||
const passwordInputRef = useRef<HTMLInputElement>();
|
const passwordInputRef = useRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
passwordInputRef?.current?.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handlePasswordChange = (e: Event) => {
|
const handlePasswordChange = (e: Event) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
if (e.target instanceof HTMLInputElement) {
|
||||||
setConfirmPassword(e.target.value);
|
setConfirmPassword(e.target.value);
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import { Icon } from '../Icon';
|
|||||||
import { formatLastSyncDate } from '@/preferences/panes/account/Sync';
|
import { formatLastSyncDate } from '@/preferences/panes/account/Sync';
|
||||||
import { SyncQueueStrategy } from '@standardnotes/snjs';
|
import { SyncQueueStrategy } from '@standardnotes/snjs';
|
||||||
import { STRING_GENERIC_SYNC_ERROR } from '@/strings';
|
import { STRING_GENERIC_SYNC_ERROR } from '@/strings';
|
||||||
import { useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { AccountMenuPane } from '.';
|
import { AccountMenuPane } from '.';
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { JSXInternal } from 'preact/src/jsx';
|
||||||
import { AppVersion } from '@/version';
|
import { AppVersion } from '@/version';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -25,6 +26,9 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
const [lastSyncDate, setLastSyncDate] = useState(
|
const [lastSyncDate, setLastSyncDate] = useState(
|
||||||
formatLastSyncDate(application.getLastSyncDate() as Date)
|
formatLastSyncDate(application.getLastSyncDate() as Date)
|
||||||
);
|
);
|
||||||
|
const [currentFocusedIndex, setCurrentFocusedIndex] = useState(0);
|
||||||
|
|
||||||
|
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
||||||
|
|
||||||
const doSynchronization = async () => {
|
const doSynchronization = async () => {
|
||||||
setIsSyncingInProgress(true);
|
setIsSyncingInProgress(true);
|
||||||
@@ -53,9 +57,49 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
|
|
||||||
const user = application.getUser();
|
const user = application.getUser();
|
||||||
|
|
||||||
|
const accountMenuRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const handleKeyDown: JSXInternal.KeyboardEventHandler<HTMLDivElement> = (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
setCurrentFocusedIndex((index) => {
|
||||||
|
console.log(index, buttonRefs.current.length);
|
||||||
|
if (index + 1 < buttonRefs.current.length) {
|
||||||
|
return index + 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
setCurrentFocusedIndex((index) => {
|
||||||
|
if (index - 1 > -1) {
|
||||||
|
return index - 1;
|
||||||
|
} else {
|
||||||
|
return buttonRefs.current.length - 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (buttonRefs.current[currentFocusedIndex]) {
|
||||||
|
buttonRefs.current[currentFocusedIndex]?.focus();
|
||||||
|
}
|
||||||
|
}, [currentFocusedIndex]);
|
||||||
|
|
||||||
|
const pushRefToArray = (ref: HTMLButtonElement | null) => {
|
||||||
|
if (ref && !buttonRefs.current.includes(ref)) {
|
||||||
|
buttonRefs.current.push(ref);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div ref={accountMenuRef} onKeyDown={handleKeyDown}>
|
||||||
<div className="flex items-center justify-between px-3 mt-1 mb-2">
|
<div className="flex items-center justify-between px-3 mt-1 mb-3">
|
||||||
<div className="sn-account-menu-headline">Account</div>
|
<div className="sn-account-menu-headline">Account</div>
|
||||||
<div className="flex cursor-pointer" onClick={closeMenu}>
|
<div className="flex cursor-pointer" onClick={closeMenu}>
|
||||||
<Icon type="close" className="color-grey-1" />
|
<Icon type="close" className="color-grey-1" />
|
||||||
@@ -105,7 +149,8 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
<div className="h-1px my-2 bg-border"></div>
|
<div className="h-1px my-2 bg-border"></div>
|
||||||
{user ? (
|
{user ? (
|
||||||
<button
|
<button
|
||||||
className="sn-dropdown-item"
|
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
ref={pushRefToArray}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appState.accountMenu.closeAccountMenu();
|
appState.accountMenu.closeAccountMenu();
|
||||||
appState.preferences.setCurrentPane('account');
|
appState.preferences.setCurrentPane('account');
|
||||||
@@ -118,7 +163,8 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="sn-dropdown-item"
|
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
ref={pushRefToArray}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMenuPane(AccountMenuPane.Register);
|
setMenuPane(AccountMenuPane.Register);
|
||||||
}}
|
}}
|
||||||
@@ -127,7 +173,8 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
Create free account
|
Create free account
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="sn-dropdown-item"
|
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
ref={pushRefToArray}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMenuPane(AccountMenuPane.SignIn);
|
setMenuPane(AccountMenuPane.SignIn);
|
||||||
}}
|
}}
|
||||||
@@ -138,7 +185,8 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="sn-dropdown-item justify-between"
|
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
ref={pushRefToArray}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appState.accountMenu.closeAccountMenu();
|
appState.accountMenu.closeAccountMenu();
|
||||||
appState.preferences.setCurrentPane('help-feedback');
|
appState.preferences.setCurrentPane('help-feedback');
|
||||||
@@ -155,7 +203,8 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
<>
|
<>
|
||||||
<div className="h-1px my-2 bg-border"></div>
|
<div className="h-1px my-2 bg-border"></div>
|
||||||
<button
|
<button
|
||||||
className="sn-dropdown-item"
|
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
ref={pushRefToArray}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appState.accountMenu.setSigningOut(true);
|
appState.accountMenu.setSigningOut(true);
|
||||||
}}
|
}}
|
||||||
@@ -165,7 +214,7 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { SignInPane } from './SignIn';
|
|||||||
import { CreateAccount } from './CreateAccount';
|
import { CreateAccount } from './CreateAccount';
|
||||||
import { ConfirmSignoutContainer } from '../ConfirmSignoutModal';
|
import { ConfirmSignoutContainer } from '../ConfirmSignoutModal';
|
||||||
import { ConfirmPassword } from './ConfirmPassword';
|
import { ConfirmPassword } from './ConfirmPassword';
|
||||||
|
import { JSXInternal } from 'preact/src/jsx';
|
||||||
|
|
||||||
export enum AccountMenuPane {
|
export enum AccountMenuPane {
|
||||||
GeneralMenu,
|
GeneralMenu,
|
||||||
@@ -87,14 +88,31 @@ const AccountMenu: FunctionComponent<Props> = observer(
|
|||||||
closeAccountMenu,
|
closeAccountMenu,
|
||||||
} = appState.accountMenu;
|
} = appState.accountMenu;
|
||||||
|
|
||||||
|
const handleKeyDown: JSXInternal.KeyboardEventHandler<HTMLDivElement> = (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
if (currentPane === AccountMenuPane.GeneralMenu) {
|
||||||
|
closeAccountMenu();
|
||||||
|
} else if (currentPane === AccountMenuPane.ConfirmPassword) {
|
||||||
|
setCurrentPane(AccountMenuPane.Register);
|
||||||
|
} else {
|
||||||
|
setCurrentPane(AccountMenuPane.GeneralMenu);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sn-component">
|
<div className="sn-component">
|
||||||
<div
|
<div
|
||||||
className={`sn-account-menu sn-dropdown ${
|
className={`sn-menu-border sn-account-menu sn-dropdown ${
|
||||||
shouldAnimateCloseMenu
|
shouldAnimateCloseMenu
|
||||||
? 'slide-up-animation'
|
? 'slide-up-animation'
|
||||||
: 'sn-dropdown--animated'
|
: 'sn-dropdown--animated'
|
||||||
} min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto absolute`}
|
} min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto absolute`}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
<MenuPaneSelector
|
<MenuPaneSelector
|
||||||
appState={appState}
|
appState={appState}
|
||||||
|
|||||||
315
app/assets/javascripts/components/QuickSettingsMenu.tsx
Normal file
315
app/assets/javascripts/components/QuickSettingsMenu.tsx
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
import {
|
||||||
|
Disclosure,
|
||||||
|
DisclosureButton,
|
||||||
|
DisclosurePanel,
|
||||||
|
} from '@reach/disclosure';
|
||||||
|
import { ContentType, SNTheme } from '@standardnotes/snjs';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
import { JSXInternal } from 'preact/src/jsx';
|
||||||
|
import { Icon } from './Icon';
|
||||||
|
import { toDirective, useCloseOnBlur } from './utils';
|
||||||
|
|
||||||
|
const MENU_CLASSNAME =
|
||||||
|
'sn-menu-border sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto';
|
||||||
|
|
||||||
|
type ThemeButtonProps = {
|
||||||
|
theme: SNTheme;
|
||||||
|
application: WebApplication;
|
||||||
|
onBlur: (event: { relatedTarget: EventTarget | null }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MenuProps = {
|
||||||
|
appState: AppState;
|
||||||
|
application: WebApplication;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThemeButton: FunctionComponent<ThemeButtonProps> = ({
|
||||||
|
application,
|
||||||
|
theme,
|
||||||
|
onBlur,
|
||||||
|
}) => {
|
||||||
|
const toggleTheme = () => {
|
||||||
|
if (theme.isLayerable() || !theme.active) {
|
||||||
|
application.toggleComponent(theme);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
onBlur={onBlur}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{theme.isLayerable() ? (
|
||||||
|
theme.active ? (
|
||||||
|
<Icon type="check" className="color-info mr-2" />
|
||||||
|
) : null
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`pseudo-radio-btn ${
|
||||||
|
theme.active ? 'pseudo-radio-btn--checked' : ''
|
||||||
|
} mr-2`}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
<span className={theme.active ? 'font-semibold' : undefined}>
|
||||||
|
{theme.package_info.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="w-5 h-5 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.package_info?.dock_icon?.background_color,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
||||||
|
({ application, appState }) => {
|
||||||
|
const { closeQuickSettingsMenu, shouldAnimateCloseMenu } =
|
||||||
|
appState.quickSettingsMenu;
|
||||||
|
const [themes, setThemes] = useState<SNTheme[]>([]);
|
||||||
|
const [themesMenuOpen, setThemesMenuOpen] = useState(false);
|
||||||
|
const [themesMenuPosition, setThemesMenuPosition] = useState({});
|
||||||
|
const [defaultThemeOn, setDefaultThemeOn] = useState(false);
|
||||||
|
|
||||||
|
const themesMenuRef = useRef<HTMLDivElement>();
|
||||||
|
const themesButtonRef = useRef<HTMLButtonElement>();
|
||||||
|
const prefsButtonRef = useRef<HTMLButtonElement>();
|
||||||
|
const quickSettingsMenuRef = useRef<HTMLDivElement>();
|
||||||
|
const defaultThemeButtonRef = useRef<HTMLButtonElement>();
|
||||||
|
|
||||||
|
const reloadThemes = useCallback(() => {
|
||||||
|
application.streamItems(ContentType.Theme, () => {
|
||||||
|
const themes = application.getDisplayableItems(
|
||||||
|
ContentType.Theme
|
||||||
|
) as SNTheme[];
|
||||||
|
setThemes(
|
||||||
|
themes.sort((a, b) => {
|
||||||
|
const aIsLayerable = a.isLayerable();
|
||||||
|
const bIsLayerable = b.isLayerable();
|
||||||
|
|
||||||
|
if (aIsLayerable && !bIsLayerable) {
|
||||||
|
return 1;
|
||||||
|
} else if (!aIsLayerable && bIsLayerable) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.package_info.name.toLowerCase() <
|
||||||
|
b.package_info.name.toLowerCase()
|
||||||
|
? -1
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setDefaultThemeOn(
|
||||||
|
!themes.find((theme) => theme.active && !theme.isLayerable())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [application]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reloadThemes();
|
||||||
|
}, [reloadThemes]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (themesMenuOpen) {
|
||||||
|
defaultThemeButtonRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [themesMenuOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
prefsButtonRef.current.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [closeOnBlur] = useCloseOnBlur(themesMenuRef, setThemesMenuOpen);
|
||||||
|
|
||||||
|
const toggleThemesMenu = () => {
|
||||||
|
if (!themesMenuOpen) {
|
||||||
|
const themesButtonRect =
|
||||||
|
themesButtonRef.current.getBoundingClientRect();
|
||||||
|
setThemesMenuPosition({
|
||||||
|
left: themesButtonRect.right,
|
||||||
|
bottom:
|
||||||
|
document.documentElement.clientHeight - themesButtonRect.bottom,
|
||||||
|
});
|
||||||
|
setThemesMenuOpen(true);
|
||||||
|
} else {
|
||||||
|
setThemesMenuOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openPreferences = () => {
|
||||||
|
closeQuickSettingsMenu();
|
||||||
|
appState.preferences.openPreferences();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBtnKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
setThemesMenuOpen(false);
|
||||||
|
themesButtonRef.current.focus();
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
if (!themesMenuOpen) {
|
||||||
|
toggleThemesMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuickSettingsKeyDown: JSXInternal.KeyboardEventHandler<HTMLDivElement> =
|
||||||
|
(event) => {
|
||||||
|
const items: NodeListOf<HTMLButtonElement> =
|
||||||
|
quickSettingsMenuRef.current.querySelectorAll(':scope > button');
|
||||||
|
const currentFocusedIndex = Array.from(items).findIndex(
|
||||||
|
(btn) => btn === document.activeElement
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!themesMenuOpen) {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
closeQuickSettingsMenu();
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
if (items[currentFocusedIndex + 1]) {
|
||||||
|
items[currentFocusedIndex + 1].focus();
|
||||||
|
} else {
|
||||||
|
items[0].focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
if (items[currentFocusedIndex - 1]) {
|
||||||
|
items[currentFocusedIndex - 1].focus();
|
||||||
|
} else {
|
||||||
|
items[items.length - 1].focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePanelKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
const themes = themesMenuRef.current.querySelectorAll('button');
|
||||||
|
const currentFocusedIndex = Array.from(themes).findIndex(
|
||||||
|
(themeBtn) => themeBtn === document.activeElement
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
case 'ArrowLeft':
|
||||||
|
event.stopPropagation();
|
||||||
|
setThemesMenuOpen(false);
|
||||||
|
themesButtonRef.current.focus();
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
if (themes[currentFocusedIndex + 1]) {
|
||||||
|
themes[currentFocusedIndex + 1].focus();
|
||||||
|
} else {
|
||||||
|
themes[0].focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
if (themes[currentFocusedIndex - 1]) {
|
||||||
|
themes[currentFocusedIndex - 1].focus();
|
||||||
|
} else {
|
||||||
|
themes[themes.length - 1].focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDefaultTheme = () => {
|
||||||
|
const activeTheme = themes.find(
|
||||||
|
(theme) => theme.active && !theme.isLayerable()
|
||||||
|
);
|
||||||
|
if (activeTheme) application.toggleComponent(activeTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sn-component">
|
||||||
|
<div
|
||||||
|
className={`sn-quick-settings-menu absolute ${MENU_CLASSNAME} ${
|
||||||
|
shouldAnimateCloseMenu
|
||||||
|
? 'slide-up-animation'
|
||||||
|
: 'sn-dropdown--animated'
|
||||||
|
}`}
|
||||||
|
ref={quickSettingsMenuRef}
|
||||||
|
onKeyDown={handleQuickSettingsKeyDown}
|
||||||
|
>
|
||||||
|
<div className="px-3 mt-1 mb-2 font-semibold color-text uppercase">
|
||||||
|
Quick Settings
|
||||||
|
</div>
|
||||||
|
<Disclosure open={themesMenuOpen} onChange={toggleThemesMenu}>
|
||||||
|
<DisclosureButton
|
||||||
|
onKeyDown={handleBtnKeyDown}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
ref={themesButtonRef}
|
||||||
|
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Icon type="themes" className="color-neutral mr-2" />
|
||||||
|
Themes
|
||||||
|
</div>
|
||||||
|
<Icon type="chevron-right" className="color-neutral" />
|
||||||
|
</DisclosureButton>
|
||||||
|
<DisclosurePanel
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
ref={themesMenuRef}
|
||||||
|
onKeyDown={handlePanelKeyDown}
|
||||||
|
style={{
|
||||||
|
...themesMenuPosition,
|
||||||
|
}}
|
||||||
|
className={`${MENU_CLASSNAME} fixed sn-dropdown--animated`}
|
||||||
|
>
|
||||||
|
<div className="px-3 my-1 font-semibold color-text uppercase">
|
||||||
|
Themes
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
onClick={toggleDefaultTheme}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
ref={defaultThemeButtonRef}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`pseudo-radio-btn ${
|
||||||
|
defaultThemeOn ? 'pseudo-radio-btn--checked' : ''
|
||||||
|
} mr-2`}
|
||||||
|
></div>
|
||||||
|
Default
|
||||||
|
</button>
|
||||||
|
{themes.map((theme) => (
|
||||||
|
<ThemeButton
|
||||||
|
theme={theme}
|
||||||
|
application={application}
|
||||||
|
key={theme.uuid}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DisclosurePanel>
|
||||||
|
</Disclosure>
|
||||||
|
<div className="h-1px my-2 bg-border"></div>
|
||||||
|
<button
|
||||||
|
class="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
|
||||||
|
onClick={openPreferences}
|
||||||
|
ref={prefsButtonRef}
|
||||||
|
>
|
||||||
|
<Icon type="more" className="color-neutral mr-2" />
|
||||||
|
Open Preferences
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const QuickSettingsMenuDirective =
|
||||||
|
toDirective<MenuProps>(QuickSettingsMenu);
|
||||||
@@ -52,6 +52,7 @@ export class AccountMenuState {
|
|||||||
shouldAnimateCloseMenu: observable,
|
shouldAnimateCloseMenu: observable,
|
||||||
|
|
||||||
setShow: action,
|
setShow: action,
|
||||||
|
setShouldAnimateClose: action,
|
||||||
toggleShow: action,
|
toggleShow: action,
|
||||||
setSigningOut: action,
|
setSigningOut: action,
|
||||||
setIsEncryptionEnabled: action,
|
setIsEncryptionEnabled: action,
|
||||||
@@ -95,11 +96,15 @@ export class AccountMenuState {
|
|||||||
this.show = show;
|
this.show = show;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setShouldAnimateClose = (shouldAnimateCloseMenu: boolean): void => {
|
||||||
|
this.shouldAnimateCloseMenu = shouldAnimateCloseMenu;
|
||||||
|
};
|
||||||
|
|
||||||
closeAccountMenu = (): void => {
|
closeAccountMenu = (): void => {
|
||||||
this.shouldAnimateCloseMenu = true;
|
this.setShouldAnimateClose(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setShow(false);
|
this.setShow(false);
|
||||||
this.shouldAnimateCloseMenu = false;
|
this.setShouldAnimateClose(false);
|
||||||
this.setCurrentPane(AccountMenuPane.GeneralMenu);
|
this.setCurrentPane(AccountMenuPane.GeneralMenu);
|
||||||
}, 150);
|
}, 150);
|
||||||
};
|
};
|
||||||
@@ -137,7 +142,11 @@ export class AccountMenuState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
toggleShow = (): void => {
|
toggleShow = (): void => {
|
||||||
this.show = !this.show;
|
if (this.show) {
|
||||||
|
this.closeAccountMenu();
|
||||||
|
} else {
|
||||||
|
this.setShow(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setOtherSessionsSignOut = (otherSessionsSignOut: boolean): void => {
|
setOtherSessionsSignOut = (otherSessionsSignOut: boolean): void => {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { NotesState } from './notes_state';
|
|||||||
import { TagsState } from './tags_state';
|
import { TagsState } from './tags_state';
|
||||||
import { AccountMenuState } from '@/ui_models/app_state/account_menu_state';
|
import { AccountMenuState } from '@/ui_models/app_state/account_menu_state';
|
||||||
import { PreferencesState } from './preferences_state';
|
import { PreferencesState } from './preferences_state';
|
||||||
|
import { QuickSettingsState } from './quick_settings_state';
|
||||||
|
|
||||||
export enum AppStateEvent {
|
export enum AppStateEvent {
|
||||||
TagChanged,
|
TagChanged,
|
||||||
@@ -62,6 +63,7 @@ export class AppState {
|
|||||||
onVisibilityChange: any;
|
onVisibilityChange: any;
|
||||||
selectedTag?: SNTag;
|
selectedTag?: SNTag;
|
||||||
showBetaWarning: boolean;
|
showBetaWarning: boolean;
|
||||||
|
readonly quickSettingsMenu = new QuickSettingsState();
|
||||||
readonly accountMenu: AccountMenuState;
|
readonly accountMenu: AccountMenuState;
|
||||||
readonly actionsMenu = new ActionsMenuState();
|
readonly actionsMenu = new ActionsMenuState();
|
||||||
readonly preferences = new PreferencesState();
|
readonly preferences = new PreferencesState();
|
||||||
@@ -105,7 +107,7 @@ export class AppState {
|
|||||||
);
|
);
|
||||||
this.accountMenu = new AccountMenuState(
|
this.accountMenu = new AccountMenuState(
|
||||||
application,
|
application,
|
||||||
this.appEventObserverRemovers,
|
this.appEventObserverRemovers
|
||||||
);
|
);
|
||||||
this.searchOptions = new SearchOptionsState(
|
this.searchOptions = new SearchOptionsState(
|
||||||
application,
|
application,
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { action, makeObservable, observable } from 'mobx';
|
||||||
|
|
||||||
|
export class QuickSettingsState {
|
||||||
|
open = false;
|
||||||
|
shouldAnimateCloseMenu = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeObservable(this, {
|
||||||
|
open: observable,
|
||||||
|
shouldAnimateCloseMenu: observable,
|
||||||
|
|
||||||
|
setOpen: action,
|
||||||
|
setShouldAnimateCloseMenu: action,
|
||||||
|
toggle: action,
|
||||||
|
closeQuickSettingsMenu: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpen = (open: boolean): void => {
|
||||||
|
this.open = open;
|
||||||
|
};
|
||||||
|
|
||||||
|
setShouldAnimateCloseMenu = (shouldAnimate: boolean): void => {
|
||||||
|
this.shouldAnimateCloseMenu = shouldAnimate;
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle = (): void => {
|
||||||
|
if (this.open) {
|
||||||
|
this.closeQuickSettingsMenu();
|
||||||
|
} else {
|
||||||
|
this.setOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
closeQuickSettingsMenu = (): void => {
|
||||||
|
this.setShouldAnimateCloseMenu(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setOpen(false);
|
||||||
|
this.setShouldAnimateCloseMenu(false);
|
||||||
|
}, 150);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -23,14 +23,22 @@
|
|||||||
ng-if='ctrl.showAccountMenu',
|
ng-if='ctrl.showAccountMenu',
|
||||||
)
|
)
|
||||||
.sk-app-bar-item.ml-0-important(
|
.sk-app-bar-item.ml-0-important(
|
||||||
ng-click='ctrl.clickPreferences()'
|
click-outside='ctrl.clickOutsideQuickSettingsMenu()',
|
||||||
|
is-open='ctrl.showQuickSettingsMenu',
|
||||||
|
ng-click='ctrl.quickSettingsPressed()'
|
||||||
)
|
)
|
||||||
.w-8.h-full.flex.items-center.justify-center.cursor-pointer
|
.w-8.h-full.flex.items-center.justify-center.cursor-pointer
|
||||||
.h-5
|
.h-5
|
||||||
icon(
|
icon(
|
||||||
type="tune"
|
type="tune"
|
||||||
class-name="rounded hover:color-info"
|
class-name="rounded hover:color-info"
|
||||||
|
ng-class="{'color-info': ctrl.showQuickSettingsMenu}"
|
||||||
)
|
)
|
||||||
|
quick-settings-menu(
|
||||||
|
ng-click='$event.stopPropagation()',
|
||||||
|
app-state='ctrl.appState'
|
||||||
|
application='ctrl.application'
|
||||||
|
ng-if='ctrl.showQuickSettingsMenu',)
|
||||||
.sk-app-bar-item
|
.sk-app-bar-item
|
||||||
a.no-decoration.sk-label.title(
|
a.no-decoration.sk-label.title(
|
||||||
href='https://standardnotes.com/help',
|
href='https://standardnotes.com/help',
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
public user?: any;
|
public user?: any;
|
||||||
private offline = true;
|
private offline = true;
|
||||||
public showAccountMenu = false;
|
public showAccountMenu = false;
|
||||||
|
public showQuickSettingsMenu = false;
|
||||||
private didCheckForOffline = false;
|
private didCheckForOffline = false;
|
||||||
private queueExtReload = false;
|
private queueExtReload = false;
|
||||||
private reloadInProgress = false;
|
private reloadInProgress = false;
|
||||||
@@ -115,6 +116,7 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
this.autorun(() => {
|
this.autorun(() => {
|
||||||
const showBetaWarning = this.appState.showBetaWarning;
|
const showBetaWarning = this.appState.showBetaWarning;
|
||||||
this.showAccountMenu = this.appState.accountMenu.show;
|
this.showAccountMenu = this.appState.accountMenu.show;
|
||||||
|
this.showQuickSettingsMenu = this.appState.quickSettingsMenu.open;
|
||||||
this.setState({
|
this.setState({
|
||||||
showBetaWarning: showBetaWarning,
|
showBetaWarning: showBetaWarning,
|
||||||
showDataUpgrade: !showBetaWarning,
|
showDataUpgrade: !showBetaWarning,
|
||||||
@@ -449,10 +451,21 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
}
|
}
|
||||||
|
|
||||||
accountMenuPressed() {
|
accountMenuPressed() {
|
||||||
|
this.appState.quickSettingsMenu.closeQuickSettingsMenu();
|
||||||
this.appState.accountMenu.toggleShow();
|
this.appState.accountMenu.toggleShow();
|
||||||
this.closeAllRooms();
|
this.closeAllRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
quickSettingsPressed() {
|
||||||
|
this.appState.accountMenu.closeAccountMenu();
|
||||||
|
if (this.themesWithIcons.length > 0) {
|
||||||
|
this.appState.quickSettingsMenu.toggle();
|
||||||
|
} else {
|
||||||
|
this.appState.preferences.openPreferences();
|
||||||
|
}
|
||||||
|
this.closeAllRooms();
|
||||||
|
}
|
||||||
|
|
||||||
toggleSyncResolutionMenu() {
|
toggleSyncResolutionMenu() {
|
||||||
this.showSyncResolution = !this.showSyncResolution;
|
this.showSyncResolution = !this.showSyncResolution;
|
||||||
}
|
}
|
||||||
@@ -476,22 +489,7 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadDockShortcuts() {
|
reloadDockShortcuts() {
|
||||||
const shortcuts = [];
|
const shortcuts: DockShortcut[] = [];
|
||||||
for (const theme of this.themesWithIcons) {
|
|
||||||
if (!theme.package_info) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const name = theme.package_info.name;
|
|
||||||
const icon = theme.package_info.dock_icon;
|
|
||||||
if (!icon) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
shortcuts.push({
|
|
||||||
name: name,
|
|
||||||
component: theme,
|
|
||||||
icon: icon,
|
|
||||||
} as DockShortcut);
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
dockShortcuts: shortcuts.sort((a, b) => {
|
dockShortcuts: shortcuts.sort((a, b) => {
|
||||||
/** Circles first, then images */
|
/** Circles first, then images */
|
||||||
@@ -556,8 +554,8 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
this.appState.accountMenu.closeAccountMenu();
|
this.appState.accountMenu.closeAccountMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
clickPreferences() {
|
clickOutsideQuickSettingsMenu() {
|
||||||
this.appState.preferences.openPreferences();
|
this.appState.quickSettingsMenu.closeQuickSettingsMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,20 @@
|
|||||||
max-height: calc(85vh - 90px);
|
max-height: calc(85vh - 90px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sn-account-menu {
|
.sn-account-menu,
|
||||||
|
.sn-quick-settings-menu {
|
||||||
z-index: $z-index-footer-bar-item-panel;
|
z-index: $z-index-footer-bar-item-panel;
|
||||||
@extend .bottom-100;
|
@extend .bottom-100;
|
||||||
@extend .left-0;
|
@extend .left-0;
|
||||||
@extend .cursor-auto;
|
@extend .cursor-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sn-menu-border {
|
||||||
|
@extend .border-1;
|
||||||
|
@extend .border-solid;
|
||||||
|
@extend .border-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
.sn-account-menu-headline {
|
.sn-account-menu-headline {
|
||||||
@extend .sk-h2;
|
@extend .sk-h2;
|
||||||
@extend .sk-bold;
|
@extend .sk-bold;
|
||||||
|
|||||||
@@ -384,3 +384,41 @@
|
|||||||
.cursor-auto {
|
.cursor-auto {
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-1\/2 {
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-1\/2 {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.-translate-1\/2 {
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pseudo-radio-btn {
|
||||||
|
@extend .w-4;
|
||||||
|
@extend .h-4;
|
||||||
|
@extend .border-2;
|
||||||
|
@extend .border-solid;
|
||||||
|
@extend .border-info;
|
||||||
|
@extend .rounded-full;
|
||||||
|
@extend .relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pseudo-radio-btn--checked::after {
|
||||||
|
content: '';
|
||||||
|
@extend .bg-info;
|
||||||
|
@extend .absolute;
|
||||||
|
@extend .top-1\/2;
|
||||||
|
@extend .left-1\/2;
|
||||||
|
@extend .-translate-1\/2;
|
||||||
|
@extend .w-2;
|
||||||
|
@extend .h-2;
|
||||||
|
@extend .rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus\:bg-info-backdrop:focus {
|
||||||
|
background-color: var(--sn-stylekit-info-backdrop-color);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user