Replace Focusable navigation wrappers with tabIndex-based focus handling
This commit is contained in:
1 parent
d336eba27d
commit
5d8cf10bf1
11 files changed
+133
-382
No files matched your search
+15
-17
@@ -2,7 +2,6 @@ import {
|
||||
Button,
|
||||
type ButtonProps,
|
||||
Column,
|
||||
Focusable,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
@@ -111,22 +110,21 @@ export function SideNav(props: any) {
|
||||
{links.map(({ id, path, label, icon }) => {
|
||||
const isSelected = pathname.startsWith(renderUrl(path, false));
|
||||
const content = (
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={icon}
|
||||
label={isCollapsed ? '' : label}
|
||||
weight={isSelected ? 'bold' : undefined}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={icon}
|
||||
label={isCollapsed ? '' : label}
|
||||
weight={isSelected ? 'bold' : undefined}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
return (
|
||||
<Link key={id} href={renderUrl(path, false)} role="button">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, Focusable, Row, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { Column, Row, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import Link from '@/components/common/Link';
|
||||
import { NavMenu } from '@/components/common/NavMenu';
|
||||
@@ -49,16 +49,15 @@ export function AdminNav({ onItemClick }: { onItemClick?: () => void }) {
|
||||
<Column gap="2">
|
||||
<Link href={renderUrl('/websites', false)} role="button" onClick={onItemClick}>
|
||||
<TooltipTrigger delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={<ArrowLeft />} label={t(labels.back)} padding />
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={<ArrowLeft />} label={t(labels.back)} padding />
|
||||
</Row>
|
||||
<Tooltip placement="right">{t(labels.back)}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</Link>
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
Button,
|
||||
Column,
|
||||
Focusable,
|
||||
ListItem,
|
||||
Row,
|
||||
Select,
|
||||
Text,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { Button, Column, ListItem, Row, Select, Text, TextField } from '@umami/react-zen';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useApi, useMessages } from '@/components/hooks';
|
||||
import { LinkSelect } from '@/components/input/LinkSelect';
|
||||
@@ -465,34 +456,34 @@ export function BoardComponentSelect({
|
||||
const Icon = def.icon;
|
||||
|
||||
return (
|
||||
<Focusable key={def.type}>
|
||||
<Row
|
||||
gap="3"
|
||||
alignItems="flex-start"
|
||||
paddingX="3"
|
||||
paddingY="2"
|
||||
borderRadius
|
||||
backgroundColor={
|
||||
selectedDef?.type === def.type ? 'surface-sunken' : undefined
|
||||
}
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleSelectComponent(def)}
|
||||
>
|
||||
<Icon size={16} />
|
||||
<Column gap="1">
|
||||
<Text
|
||||
size="sm"
|
||||
weight={selectedDef?.type === def.type ? 'bold' : undefined}
|
||||
>
|
||||
{def.name}
|
||||
</Text>
|
||||
<Text size="xs" color="muted">
|
||||
{def.description}
|
||||
</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
key={def.type}
|
||||
tabIndex={0}
|
||||
gap="3"
|
||||
alignItems="flex-start"
|
||||
paddingX="3"
|
||||
paddingY="2"
|
||||
borderRadius
|
||||
backgroundColor={
|
||||
selectedDef?.type === def.type ? 'surface-sunken' : undefined
|
||||
}
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleSelectComponent(def)}
|
||||
>
|
||||
<Icon size={16} />
|
||||
<Column gap="1">
|
||||
<Text
|
||||
size="sm"
|
||||
weight={selectedDef?.type === def.type ? 'bold' : undefined}
|
||||
>
|
||||
{def.name}
|
||||
</Text>
|
||||
<Text size="xs" color="muted">
|
||||
{def.description}
|
||||
</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</Column>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, Focusable, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { Column, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import Link from '@/components/common/Link';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
@@ -60,16 +60,15 @@ export function SettingsNav({
|
||||
<Link href={renderUrl('/websites', false)} role="button" onClick={onItemClick}>
|
||||
{(() => {
|
||||
const content = (
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={<ArrowLeft />} label={isCollapsed ? '' : t(labels.back)} padding />
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={<ArrowLeft />} label={isCollapsed ? '' : t(labels.back)} padding />
|
||||
</Row>
|
||||
);
|
||||
|
||||
return isCollapsed ? (
|
||||
@@ -92,22 +91,21 @@ export function SettingsNav({
|
||||
{sectionItems.map(({ id, path, label, icon }) => {
|
||||
const isSelected = selectedKey === id;
|
||||
const content = (
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={icon}
|
||||
label={isCollapsed ? '' : label}
|
||||
weight={isSelected ? 'bold' : undefined}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={icon}
|
||||
label={isCollapsed ? '' : label}
|
||||
weight={isSelected ? 'bold' : undefined}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
return (
|
||||
<Link key={id} href={path} role="button" onClick={onItemClick}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, Focusable, Icon, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { Column, Icon, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import classNames from 'classnames';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { firstBy } from 'thenby';
|
||||
@@ -232,9 +232,7 @@ export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyP
|
||||
<div>{formatLongNumber(nodeCount)}</div>
|
||||
) : (
|
||||
<TooltipTrigger delay={0}>
|
||||
<Focusable>
|
||||
<div>{formatLongNumber(nodeCount)}</div>
|
||||
</Focusable>
|
||||
<div tabIndex={0}>{formatLongNumber(nodeCount)}</div>
|
||||
<Tooltip placement="top" sideOffset={20} showArrow>
|
||||
<Text transform="lowercase" color="red">
|
||||
{`${dropped}% ${t(labels.dropoff)}`}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, Focusable, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { Column, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import Link from '@/components/common/Link';
|
||||
import { useMessages, useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
||||
@@ -22,16 +22,15 @@ export function WebsiteNav({
|
||||
<Link href={renderUrl('/websites', false)} role="button" onClick={onItemClick}>
|
||||
{isCollapsed ? (
|
||||
<TooltipTrigger delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={<ArrowLeft />} label="" padding />
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={<ArrowLeft />} label="" padding />
|
||||
</Row>
|
||||
<Tooltip placement="right">{t(labels.back)}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
) : (
|
||||
@@ -58,22 +57,21 @@ export function WebsiteNav({
|
||||
<Link key={id} href={path} role="button" onClick={onItemClick}>
|
||||
{isCollapsed ? (
|
||||
<TooltipTrigger delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={icon}
|
||||
label=""
|
||||
weight={isSelected ? 'bold' : undefined}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={icon}
|
||||
label=""
|
||||
weight={isSelected ? 'bold' : undefined}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
<Tooltip placement="right">{label}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
Button,
|
||||
Column,
|
||||
Focusable,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
@@ -179,22 +178,17 @@ export function ShareNav({
|
||||
<Link key={id} href={path} role="button" onClick={onItemClick}>
|
||||
{collapsed ? (
|
||||
<TooltipTrigger delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={icon}
|
||||
label=""
|
||||
weight={isSelected ? 'bold' : undefined}
|
||||
/>
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={icon} label="" weight={isSelected ? 'bold' : undefined} />
|
||||
</Row>
|
||||
<Tooltip placement="right">{label}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
) : (
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import {
|
||||
Column,
|
||||
Icon,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuSection,
|
||||
MenuSeparator,
|
||||
MenuTrigger,
|
||||
Popover,
|
||||
Pressable,
|
||||
Row,
|
||||
SubmenuTrigger,
|
||||
Text,
|
||||
} from '@umami/react-zen';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import type { Key } from 'react';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { useLoginQuery, useMessages, useMobile, useNavigation } from '@/components/hooks';
|
||||
import { ChevronRight, User, Users } from '@/components/icons';
|
||||
import { Switch } from '@/components/svg';
|
||||
import { LAST_TEAM_CONFIG } from '@/lib/constants';
|
||||
import { removeItem } from '@/lib/storage';
|
||||
|
||||
export interface TeamsButtonProps {
|
||||
showText?: boolean;
|
||||
onAction?: (id: any) => void;
|
||||
}
|
||||
|
||||
export function NavButton({ showText = true }: TeamsButtonProps) {
|
||||
const { user } = useLoginQuery();
|
||||
const { t, labels } = useMessages();
|
||||
const { teamId, router } = useNavigation();
|
||||
const { isMobile } = useMobile();
|
||||
const team = user?.teams?.find(({ id }) => id === teamId);
|
||||
const selectedKeys = new Set([teamId || 'user']);
|
||||
const label = teamId ? team?.name : user.username;
|
||||
const cloudMode = !!process.env.cloudMode;
|
||||
|
||||
const getUrl = (url: string) => {
|
||||
return cloudMode ? `${process.env.cloudUrl}${url}` : url;
|
||||
};
|
||||
|
||||
const handleAction = async (key: Key) => {
|
||||
if (key === 'user') {
|
||||
removeItem(LAST_TEAM_CONFIG);
|
||||
if (cloudMode) {
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleNavigate = (url: string) => {
|
||||
if (cloudMode) {
|
||||
window.location.href = url;
|
||||
} else {
|
||||
router.push(url);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuTrigger>
|
||||
<Pressable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
flexGrow={1}
|
||||
padding
|
||||
border
|
||||
borderRadius
|
||||
shadow="sm"
|
||||
minHeight="40px"
|
||||
role="button"
|
||||
style={{ cursor: 'pointer', textWrap: 'nowrap', overflow: 'hidden', outline: 'none' }}
|
||||
>
|
||||
<Row alignItems="center" position="relative" gap maxHeight="40px">
|
||||
<Icon>{teamId ? <Users /> : <User />}</Icon>
|
||||
{showText && <Text>{label}</Text>}
|
||||
</Row>
|
||||
{showText && (
|
||||
<Icon rotate={90} size="sm">
|
||||
<ChevronRight />
|
||||
</Icon>
|
||||
)}
|
||||
</Row>
|
||||
</Pressable>
|
||||
<Popover side="bottom" align="start">
|
||||
<Column minWidth="300px">
|
||||
<Menu>
|
||||
<SubmenuTrigger>
|
||||
<MenuItem id="teams" showChecked={false} showSubMenuIcon>
|
||||
<IconLabel icon={<Switch />} label={t(labels.switchAccount)} />
|
||||
</MenuItem>
|
||||
<Popover side={isMobile ? 'bottom' : 'right'} align="start">
|
||||
<Column minWidth="300px">
|
||||
<Menu selectionMode="single" selectedKeys={selectedKeys}>
|
||||
<MenuSection title={t(labels.myAccount)}>
|
||||
<MenuItem id="user" onAction={handleAction}>
|
||||
<IconLabel icon={<User />} label={user.username} />
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
<MenuSeparator />
|
||||
<MenuSection title={t(labels.teams)}>
|
||||
{user?.teams?.map(({ id, name }) => (
|
||||
<MenuItem
|
||||
key={id}
|
||||
id={id}
|
||||
onAction={() => handleNavigate(getUrl(`/teams/${id}`))}
|
||||
>
|
||||
<IconLabel icon={<Users />}>
|
||||
<Text wrap="nowrap">{name}</Text>
|
||||
</IconLabel>
|
||||
</MenuItem>
|
||||
))}
|
||||
{user?.teams?.length === 0 && (
|
||||
<MenuItem id="manage-teams">
|
||||
<a href="/settings/teams" style={{ width: '100%' }}>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Text align="center">Manage teams</Text>
|
||||
<Icon>
|
||||
<ArrowRight />
|
||||
</Icon>
|
||||
</Row>
|
||||
</a>
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuSection>
|
||||
</Menu>
|
||||
</Column>
|
||||
</Popover>
|
||||
</SubmenuTrigger>
|
||||
</Menu>
|
||||
</Column>
|
||||
</Popover>
|
||||
</MenuTrigger>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Focusable, Grid, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { Grid, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { addHours, format, startOfDay } from 'date-fns';
|
||||
import { Fragment } from 'react';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
@@ -77,26 +77,25 @@ export function WeeklyTraffic({ websiteId }: { websiteId: string }) {
|
||||
{day?.map((count: number, j) => {
|
||||
const pct = max ? count / max : 0;
|
||||
const cell = (
|
||||
<Focusable>
|
||||
<Row
|
||||
tabIndex={0}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor="surface-raised"
|
||||
width="16px"
|
||||
height="16px"
|
||||
borderRadius="full"
|
||||
style={{ margin: '0 auto' }}
|
||||
role="button"
|
||||
>
|
||||
<Row
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor="surface-raised"
|
||||
backgroundColor="primary"
|
||||
width="16px"
|
||||
height="16px"
|
||||
borderRadius="full"
|
||||
style={{ margin: '0 auto' }}
|
||||
role="button"
|
||||
>
|
||||
<Row
|
||||
backgroundColor="primary"
|
||||
width="16px"
|
||||
height="16px"
|
||||
borderRadius="full"
|
||||
style={{ opacity: pct, transform: `scale(${pct})` }}
|
||||
/>
|
||||
</Row>
|
||||
</Focusable>
|
||||
style={{ opacity: pct, transform: `scale(${pct})` }}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
|
||||
if (count <= 0) {
|
||||
|
||||
Reference in new issue
Block a user