mirror of
https://github.com/urbit/shrub.git
synced 2025-01-01 17:16:47 +03:00
nav: fixing dimming and inter nav clicks
This commit is contained in:
parent
f8bfbf1bbb
commit
65b9f229c5
@ -22,7 +22,7 @@ const variants: Record<ButtonVariant, string> = {
|
||||
secondary: 'text-black bg-gray-100',
|
||||
caution: 'text-white bg-orange-400',
|
||||
destructive: 'text-white bg-red-500',
|
||||
'alt-primary': 'text-white bg-blue-400',
|
||||
'alt-primary': 'text-white bg-blue-400 ring-blue-300',
|
||||
'alt-secondary': 'text-blue-400 bg-blue-50'
|
||||
};
|
||||
|
||||
|
@ -35,212 +35,215 @@ type LeapProps = {
|
||||
menu: MenuState;
|
||||
dropdown: string;
|
||||
navOpen: boolean;
|
||||
shouldDim: boolean;
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const Leap = React.forwardRef(({ menu, dropdown, navOpen, className }: LeapProps, ref) => {
|
||||
const { push } = useHistory();
|
||||
const match = useRouteMatch<{ menu?: MenuState; query?: string; desk?: string }>(
|
||||
`/leap/${menu}/:query?/(apps)?/:desk?`
|
||||
);
|
||||
const appsMatch = useRouteMatch(`/leap/${menu}/${match?.params.query}/apps`);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useImperativeHandle(ref, () => inputRef.current);
|
||||
const { rawInput, selectedMatch, matches, selection, select } = useLeapStore();
|
||||
export const Leap = React.forwardRef(
|
||||
({ menu, dropdown, navOpen, shouldDim, className }: LeapProps, ref) => {
|
||||
const { push } = useHistory();
|
||||
const match = useRouteMatch<{ menu?: MenuState; query?: string; desk?: string }>(
|
||||
`/leap/${menu}/:query?/(apps)?/:desk?`
|
||||
);
|
||||
const appsMatch = useRouteMatch(`/leap/${menu}/${match?.params.query}/apps`);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useImperativeHandle(ref, () => inputRef.current);
|
||||
const { rawInput, selectedMatch, matches, selection, select } = useLeapStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (selection && rawInput === '') {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [selection, rawInput]);
|
||||
useEffect(() => {
|
||||
if (selection && rawInput === '') {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [selection, rawInput]);
|
||||
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (selection || menu === 'search') {
|
||||
return;
|
||||
}
|
||||
|
||||
push('/leap/search');
|
||||
}, [selection, menu]);
|
||||
|
||||
const onFocus = useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
// refocusing tab with input focused is false trigger
|
||||
const windowFocus = e.nativeEvent.currentTarget === document.body;
|
||||
if (windowFocus) {
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (selection || menu === 'search') {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleSearch();
|
||||
},
|
||||
[toggleSearch]
|
||||
);
|
||||
push('/leap/search');
|
||||
}, [selection, menu]);
|
||||
|
||||
const getMatch = useCallback(
|
||||
(value: string) => {
|
||||
const normValue = value.toLocaleLowerCase();
|
||||
return matches.find(
|
||||
(m) =>
|
||||
m.display?.toLocaleLowerCase().startsWith(normValue) ||
|
||||
m.value.toLocaleLowerCase().startsWith(normValue)
|
||||
);
|
||||
},
|
||||
[matches]
|
||||
);
|
||||
const onFocus = useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
// refocusing tab with input focused is false trigger
|
||||
const windowFocus = e.nativeEvent.currentTarget === document.body;
|
||||
if (windowFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
const navigateByInput = useCallback(
|
||||
(input: string) => {
|
||||
const normalizedValue = input.trim().replace(/(~?[\w^_-]{3,13})\//, '$1/apps/');
|
||||
push(`/leap/${menu}/${normalizedValue}`);
|
||||
},
|
||||
[menu]
|
||||
);
|
||||
toggleSearch();
|
||||
},
|
||||
[toggleSearch]
|
||||
);
|
||||
|
||||
const debouncedSearch = useDebounce(
|
||||
(input: string) => {
|
||||
if (!match || appsMatch) {
|
||||
return;
|
||||
}
|
||||
const getMatch = useCallback(
|
||||
(value: string) => {
|
||||
const normValue = value.toLocaleLowerCase();
|
||||
return matches.find(
|
||||
(m) =>
|
||||
m.display?.toLocaleLowerCase().startsWith(normValue) ||
|
||||
m.value.toLocaleLowerCase().startsWith(normValue)
|
||||
);
|
||||
},
|
||||
[matches]
|
||||
);
|
||||
|
||||
useLeapStore.setState({ searchInput: input });
|
||||
navigateByInput(input);
|
||||
},
|
||||
300,
|
||||
{ leading: true }
|
||||
);
|
||||
const navigateByInput = useCallback(
|
||||
(input: string) => {
|
||||
const normalizedValue = input.trim().replace(/(~?[\w^_-]{3,13})\//, '$1/apps/');
|
||||
push(`/leap/${menu}/${normalizedValue}`);
|
||||
},
|
||||
[menu]
|
||||
);
|
||||
|
||||
const handleSearch = useCallback(debouncedSearch, [match]);
|
||||
const debouncedSearch = useDebounce(
|
||||
(input: string) => {
|
||||
if (!match || appsMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
const isDeletion = (e.nativeEvent as InputEvent).inputType === 'deleteContentBackward';
|
||||
const inputMatch = getMatch(value);
|
||||
const matchValue = inputMatch?.display || inputMatch?.value;
|
||||
useLeapStore.setState({ searchInput: input });
|
||||
navigateByInput(input);
|
||||
},
|
||||
300,
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
if (matchValue && inputRef.current && !isDeletion) {
|
||||
inputRef.current.value = matchValue;
|
||||
inputRef.current.setSelectionRange(value.length, matchValue.length);
|
||||
useLeapStore.setState({
|
||||
rawInput: matchValue,
|
||||
selectedMatch: inputMatch
|
||||
});
|
||||
} else {
|
||||
useLeapStore.setState({
|
||||
rawInput: value,
|
||||
selectedMatch: matches[0]
|
||||
});
|
||||
}
|
||||
const handleSearch = useCallback(debouncedSearch, [match]);
|
||||
|
||||
handleSearch(value);
|
||||
},
|
||||
[matches]
|
||||
);
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
const isDeletion = (e.nativeEvent as InputEvent).inputType === 'deleteContentBackward';
|
||||
const inputMatch = getMatch(value);
|
||||
const matchValue = inputMatch?.display || inputMatch?.value;
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (matchValue && inputRef.current && !isDeletion) {
|
||||
inputRef.current.value = matchValue;
|
||||
inputRef.current.setSelectionRange(value.length, matchValue.length);
|
||||
useLeapStore.setState({
|
||||
rawInput: matchValue,
|
||||
selectedMatch: inputMatch
|
||||
});
|
||||
} else {
|
||||
useLeapStore.setState({
|
||||
rawInput: value,
|
||||
selectedMatch: matches[0]
|
||||
});
|
||||
}
|
||||
|
||||
const value = inputRef.current?.value.trim();
|
||||
const currentMatch = selectedMatch || (value && getMatch(value));
|
||||
handleSearch(value);
|
||||
},
|
||||
[matches]
|
||||
);
|
||||
|
||||
if (!currentMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMatch?.openInNewTab) {
|
||||
window.open(currentMatch.url, currentMatch.value);
|
||||
return;
|
||||
}
|
||||
|
||||
push(currentMatch.url);
|
||||
useLeapStore.setState({ rawInput: '' });
|
||||
},
|
||||
[match, selectedMatch]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLDivElement>) => {
|
||||
const deletion = e.key === 'Backspace' || e.key === 'Delete';
|
||||
const arrow = e.key === 'ArrowDown' || e.key === 'ArrowUp';
|
||||
|
||||
if (deletion && !rawInput && selection) {
|
||||
e.preventDefault();
|
||||
select(null, appsMatch && !appsMatch.isExact ? undefined : match?.params.query);
|
||||
const pathBack = createPreviousPath(match?.url || '');
|
||||
push(pathBack);
|
||||
}
|
||||
|
||||
if (arrow) {
|
||||
const onSubmit = useCallback(
|
||||
(e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const currentIndex = selectedMatch
|
||||
? matches.findIndex((m) => {
|
||||
const matchValue = m.display || m.value;
|
||||
const searchValue = selectedMatch.display || selectedMatch.value;
|
||||
return matchValue === searchValue;
|
||||
})
|
||||
: 0;
|
||||
const unsafeIndex = e.key === 'ArrowUp' ? currentIndex - 1 : currentIndex + 1;
|
||||
const index = (unsafeIndex + matches.length) % matches.length;
|
||||
const value = inputRef.current?.value.trim();
|
||||
const currentMatch = selectedMatch || (value && getMatch(value));
|
||||
|
||||
const newMatch = matches[index];
|
||||
const matchValue = newMatch.display || newMatch.value;
|
||||
useLeapStore.setState({
|
||||
rawInput: matchValue,
|
||||
// searchInput: matchValue,
|
||||
selectedMatch: newMatch
|
||||
});
|
||||
}
|
||||
},
|
||||
[selection, rawInput, match, matches, selectedMatch]
|
||||
);
|
||||
if (!currentMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative z-50 w-full">
|
||||
<form
|
||||
className={classNames(
|
||||
'flex items-center h-full w-full px-2 rounded-full bg-white default-ring focus-within:ring-2',
|
||||
navOpen && menu !== 'search' && 'opacity-60',
|
||||
!navOpen ? 'bg-gray-50' : '',
|
||||
className
|
||||
)}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<label
|
||||
htmlFor="leap"
|
||||
if (currentMatch?.openInNewTab) {
|
||||
window.open(currentMatch.url, currentMatch.value);
|
||||
return;
|
||||
}
|
||||
|
||||
push(currentMatch.url);
|
||||
useLeapStore.setState({ rawInput: '' });
|
||||
},
|
||||
[match, selectedMatch]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLDivElement>) => {
|
||||
const deletion = e.key === 'Backspace' || e.key === 'Delete';
|
||||
const arrow = e.key === 'ArrowDown' || e.key === 'ArrowUp';
|
||||
|
||||
if (deletion && !rawInput && selection) {
|
||||
e.preventDefault();
|
||||
select(null, appsMatch && !appsMatch.isExact ? undefined : match?.params.query);
|
||||
const pathBack = createPreviousPath(match?.url || '');
|
||||
push(pathBack);
|
||||
}
|
||||
|
||||
if (arrow) {
|
||||
e.preventDefault();
|
||||
|
||||
const currentIndex = selectedMatch
|
||||
? matches.findIndex((m) => {
|
||||
const matchValue = m.display || m.value;
|
||||
const searchValue = selectedMatch.display || selectedMatch.value;
|
||||
return matchValue === searchValue;
|
||||
})
|
||||
: 0;
|
||||
const unsafeIndex = e.key === 'ArrowUp' ? currentIndex - 1 : currentIndex + 1;
|
||||
const index = (unsafeIndex + matches.length) % matches.length;
|
||||
|
||||
const newMatch = matches[index];
|
||||
const matchValue = newMatch.display || newMatch.value;
|
||||
useLeapStore.setState({
|
||||
rawInput: matchValue,
|
||||
// searchInput: matchValue,
|
||||
selectedMatch: newMatch
|
||||
});
|
||||
}
|
||||
},
|
||||
[selection, rawInput, match, matches, selectedMatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative z-50 w-full">
|
||||
<form
|
||||
className={classNames(
|
||||
'inline-block flex-none p-2 h4 text-blue-400',
|
||||
!selection && 'sr-only'
|
||||
'flex items-center h-full w-full px-2 rounded-full bg-white default-ring focus-within:ring-2',
|
||||
shouldDim && 'opacity-60',
|
||||
!navOpen ? 'bg-gray-50' : '',
|
||||
className
|
||||
)}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{selection || 'Search Landscape'}
|
||||
</label>
|
||||
<input
|
||||
id="leap"
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
placeholder={selection ? '' : 'Search Landscape'}
|
||||
className="flex-1 w-full h-full px-2 h4 rounded-full bg-transparent outline-none"
|
||||
value={rawInput}
|
||||
onClick={toggleSearch}
|
||||
onFocus={onFocus}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
aria-autocomplete="both"
|
||||
aria-controls={dropdown}
|
||||
aria-activedescendant={selectedMatch?.display || selectedMatch?.value}
|
||||
/>
|
||||
</form>
|
||||
{navOpen && (
|
||||
<Link
|
||||
to="/"
|
||||
className="absolute top-1/2 right-2 flex-none circle-button w-8 h-8 text-gray-400 bg-gray-50 default-ring -translate-y-1/2"
|
||||
onClick={() => select(null)}
|
||||
>
|
||||
<Cross className="w-3 h-3 fill-current" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
<label
|
||||
htmlFor="leap"
|
||||
className={classNames(
|
||||
'inline-block flex-none p-2 h4 text-blue-400',
|
||||
!selection && 'sr-only'
|
||||
)}
|
||||
>
|
||||
{selection || 'Search Landscape'}
|
||||
</label>
|
||||
<input
|
||||
id="leap"
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
placeholder={selection ? '' : 'Search Landscape'}
|
||||
className="flex-1 w-full h-full px-2 h4 rounded-full bg-transparent outline-none"
|
||||
value={rawInput}
|
||||
onClick={toggleSearch}
|
||||
onFocus={onFocus}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
aria-autocomplete="both"
|
||||
aria-controls={dropdown}
|
||||
aria-activedescendant={selectedMatch?.display || selectedMatch?.value}
|
||||
/>
|
||||
</form>
|
||||
{navOpen && (
|
||||
<Link
|
||||
to="/"
|
||||
className="absolute top-1/2 right-2 flex-none circle-button w-8 h-8 text-gray-400 bg-gray-50 default-ring -translate-y-1/2"
|
||||
onClick={() => select(null)}
|
||||
>
|
||||
<Cross className="w-3 h-3 fill-current" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -52,33 +52,6 @@ export type MenuState =
|
||||
| 'help-and-support'
|
||||
| 'system-preferences';
|
||||
|
||||
export function createNextPath(current: string, nextPart?: string): string {
|
||||
let end = nextPart;
|
||||
const parts = current.split('/').reverse();
|
||||
if (parts[1] === 'search') {
|
||||
end = 'apps';
|
||||
}
|
||||
|
||||
if (parts[0] === 'leap') {
|
||||
end = `search/${nextPart}`;
|
||||
}
|
||||
|
||||
return `${current}/${end}`;
|
||||
}
|
||||
|
||||
export function createPreviousPath(current: string): string {
|
||||
const parts = current.split('/');
|
||||
parts.pop();
|
||||
|
||||
if (parts[parts.length - 1] === 'leap') {
|
||||
parts.push('search');
|
||||
}
|
||||
if (parts[parts.length - 2] === 'apps') {
|
||||
parts.pop();
|
||||
}
|
||||
return parts.join('/');
|
||||
}
|
||||
|
||||
interface NavProps {
|
||||
menu?: MenuState;
|
||||
}
|
||||
@ -123,6 +96,15 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const preventClose = useCallback((e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const hasNavAncestor = target.closest('#dialog-nav');
|
||||
|
||||
if (hasNavAncestor) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Using portal so that we can retain the same nav items both in the dialog and in the base header */}
|
||||
@ -132,12 +114,20 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
>
|
||||
<SystemMenu
|
||||
open={!!systemMenuOpen}
|
||||
menu={menuState}
|
||||
navOpen={isOpen}
|
||||
shouldDim={isOpen && menu !== 'system-preferences' && menu !== 'help-and-support'}
|
||||
className={classNames('relative z-50 flex-none', eitherOpen ? 'bg-white' : 'bg-gray-50')}
|
||||
/>
|
||||
<NotificationsLink menu={menuState} navOpen={isOpen} />
|
||||
<Leap ref={inputRef} menu={menuState} dropdown="leap-items" navOpen={isOpen} />
|
||||
<NotificationsLink
|
||||
navOpen={isOpen}
|
||||
shouldDim={(isOpen && menu !== 'notifications') || !!systemMenuOpen}
|
||||
/>
|
||||
<Leap
|
||||
ref={inputRef}
|
||||
menu={menuState}
|
||||
dropdown="leap-items"
|
||||
navOpen={isOpen}
|
||||
shouldDim={(isOpen && menu !== 'search') || !!systemMenuOpen}
|
||||
/>
|
||||
</Portal.Root>
|
||||
<div
|
||||
ref={navRef}
|
||||
@ -152,6 +142,7 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
/>
|
||||
<Dialog open={isOpen} onOpenChange={onDialogClose}>
|
||||
<DialogContent
|
||||
onInteractOutside={preventClose}
|
||||
onOpenAutoFocus={onOpen}
|
||||
className="fixed bottom-0 sm:top-0 sm:bottom-auto scroll-left-50 flex flex-col scroll-full-width max-w-[882px] px-4 sm:pb-4 text-gray-400 -translate-x-1/2 outline-none"
|
||||
role="combobox"
|
||||
@ -160,6 +151,7 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<header
|
||||
id="dialog-nav"
|
||||
ref={dialogNavRef}
|
||||
className="max-w-[712px] w-full mx-auto my-6 sm:mb-3 order-last sm:order-none"
|
||||
/>
|
||||
|
@ -4,7 +4,6 @@ import { Link, LinkProps } from 'react-router-dom';
|
||||
import { Bullet } from '../components/icons/Bullet';
|
||||
import { Notification } from '../state/hark-types';
|
||||
import { useNotifications } from '../state/notifications';
|
||||
import { MenuState } from './Nav';
|
||||
|
||||
type NotificationsState = 'empty' | 'unread' | 'attention-needed';
|
||||
|
||||
@ -25,11 +24,11 @@ function getNotificationsState(
|
||||
}
|
||||
|
||||
type NotificationsLinkProps = Omit<LinkProps<HTMLAnchorElement>, 'to'> & {
|
||||
menu: MenuState;
|
||||
navOpen: boolean;
|
||||
shouldDim: boolean;
|
||||
};
|
||||
|
||||
export const NotificationsLink = ({ navOpen, menu }: NotificationsLinkProps) => {
|
||||
export const NotificationsLink = ({ navOpen, shouldDim }: NotificationsLinkProps) => {
|
||||
const { notifications, systemNotifications } = useNotifications();
|
||||
const state = getNotificationsState(notifications, systemNotifications);
|
||||
|
||||
@ -39,7 +38,7 @@ export const NotificationsLink = ({ navOpen, menu }: NotificationsLinkProps) =>
|
||||
className={classNames(
|
||||
'relative z-50 flex-none circle-button h4 default-ring',
|
||||
navOpen && 'text-opacity-60',
|
||||
navOpen && menu !== 'notifications' && 'opacity-60',
|
||||
shouldDim && 'opacity-60',
|
||||
state === 'empty' && !navOpen && 'text-gray-400 bg-gray-50',
|
||||
state === 'empty' && navOpen && 'text-gray-400 bg-white',
|
||||
state === 'unread' && 'bg-blue-400 text-white',
|
||||
|
@ -7,13 +7,11 @@ import { Vat } from '@urbit/api/hood';
|
||||
import { Adjust } from '../components/icons/Adjust';
|
||||
import { useVat } from '../state/kiln';
|
||||
import { disableDefault, handleDropdownLink } from '../state/util';
|
||||
import { MenuState } from './Nav';
|
||||
import { useMedia } from '../logic/useMedia';
|
||||
|
||||
type SystemMenuProps = HTMLAttributes<HTMLButtonElement> & {
|
||||
menu: MenuState;
|
||||
open: boolean;
|
||||
navOpen: boolean;
|
||||
shouldDim: boolean;
|
||||
};
|
||||
|
||||
function getHash(vat: Vat): string {
|
||||
@ -21,7 +19,7 @@ function getHash(vat: Vat): string {
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
export const SystemMenu = ({ className, menu, open, navOpen }: SystemMenuProps) => {
|
||||
export const SystemMenu = ({ className, open, shouldDim }: SystemMenuProps) => {
|
||||
const { push } = useHistory();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const garden = useVat('garden');
|
||||
@ -61,10 +59,7 @@ export const SystemMenu = ({ className, menu, open, navOpen }: SystemMenuProps)
|
||||
className={classNames(
|
||||
'appearance-none circle-button default-ring',
|
||||
open && 'text-gray-300',
|
||||
navOpen &&
|
||||
menu !== 'system-preferences' &&
|
||||
menu !== 'help-and-support' &&
|
||||
'opacity-60',
|
||||
shouldDim && 'opacity-60',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
@ -53,8 +53,8 @@ export const SystemPreferences = (props: RouteComponentProps<{ submenu: string }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-[600px] max-h-full">
|
||||
<aside className="flex-none min-w-60 border-r-2 border-gray-50">
|
||||
<div className="flex h-full overflow-y-auto">
|
||||
<aside className="flex-none min-w-60">
|
||||
<div className="p-8">
|
||||
<input className="input h4 default-ring bg-gray-50" placeholder="Search Preferences" />
|
||||
</div>
|
||||
@ -84,7 +84,7 @@ export const SystemPreferences = (props: RouteComponentProps<{ submenu: string }
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<section className="flex-1 p-8 text-black">
|
||||
<section className="flex-1 min-h-[600px] p-8 text-black border-l-2 border-gray-50">
|
||||
<Switch>
|
||||
<Route path={`${match.url}/system-updates`} component={SystemUpdatePrefs} />
|
||||
<Route path={`${match.url}/interface`} component={InterfacePrefs} />
|
||||
|
@ -11,6 +11,10 @@ export const TileInfo = () => {
|
||||
const charge = useCharge(desk);
|
||||
const vat = useVat(desk);
|
||||
|
||||
if (!charge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && push('/')}>
|
||||
<DialogContent>
|
||||
|
Loading…
Reference in New Issue
Block a user