Skip to content

Commit

Permalink
Use 'role' = button for chip navigation (#8011)
Browse files Browse the repository at this point in the history
Closes #7817
Added role attribute to the div element of the Chip component. This
assigns the role of "button" to the container, which is important for
accessibility. It indicates that this div should be treated as a button
by assistive technologies like screen readers.

---------

Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Félix Malfait <[email protected]>
  • Loading branch information
3 people authored Oct 24, 2024
1 parent 445ab83 commit 4e59f00
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AvatarChip, AvatarChipVariant, UndecoratedLink } from 'twenty-ui';
import { AvatarChip, AvatarChipVariant } from 'twenty-ui';

import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
Expand All @@ -23,24 +23,20 @@ export const RecordChip = ({
record,
});

const handleClick = (e: MouseEvent<HTMLAnchorElement>) => {
const handleClick = (e: MouseEvent<Element>) => {
e.stopPropagation();
};

return (
<UndecoratedLink
<AvatarChip
placeholderColorSeed={record.id}
name={recordChipData.name}
avatarType={recordChipData.avatarType}
avatarUrl={recordChipData.avatarUrl ?? ''}
className={className}
variant={variant}
onClick={handleClick}
to={getLinkToShowPage(objectNameSingular, record)}
>
<AvatarChip
placeholderColorSeed={record.id}
name={recordChipData.name}
avatarType={recordChipData.avatarType}
avatarUrl={recordChipData.avatarUrl ?? ''}
className={className}
variant={variant}
onClick={() => {}}
/>
</UndecoratedLink>
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,12 @@ export const RecordIdentifierChip = ({
variant,
size,
}: RecordIdentifierChipProps) => {
const { onIndexIdentifierClick } = useContext(RecordIndexRootPropsContext);
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext);
const { recordChipData } = useRecordChipData({
objectNameSingular,
record,
});

const handleAvatarChipClick = () => {
onIndexIdentifierClick(record.id);
};

const { Icon: LeftIcon, IconColor: LeftIconColor } =
useGetStandardObjectIcon(objectNameSingular);
return (
Expand All @@ -36,7 +32,7 @@ export const RecordIdentifierChip = ({
name={recordChipData.name}
avatarType={recordChipData.avatarType}
avatarUrl={recordChipData.avatarUrl ?? ''}
onClick={handleAvatarChipClick}
to={indexIdentifierUrl(record.id)}
variant={variant}
LeftIcon={LeftIcon}
LeftIconColor={LeftIconColor}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { createRootPropsContext } from '~/utils/createRootPropsContext';

export type RecordIndexRootPropsContextProps = {
onIndexIdentifierClick: (recordId: string) => void;
indexIdentifierUrl: (recordId: string) => string;
onIndexRecordsLoaded: () => void;
onCreateRecord: () => void;
objectNamePlural: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { useNavigate } from 'react-router-dom';

export const useHandleIndexIdentifierClick = ({
objectMetadataItem,
Expand All @@ -11,22 +10,20 @@ export const useHandleIndexIdentifierClick = ({
recordIndexId: string;
objectMetadataItem: ObjectMetadataItem;
}) => {
const navigate = useNavigate();

const currentViewId = useRecoilComponentValueV2(
currentViewIdComponentState,
recordIndexId,
);

const handleIndexIdentifierClick = (recordId: string) => {
const indexIdentifierUrl = (recordId: string) => {
const showPageURL = buildShowPageURL(
objectMetadataItem.nameSingular,
recordId,
currentViewId,
);

navigate(showPageURL);
return showPageURL;
};

return { handleIndexIdentifierClick };
return { indexIdentifierUrl };
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { isDefined } from '~/utils/isDefined';

import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';

export const DEFAULT_CELL_SCOPE: HotkeyScope = {
Expand All @@ -41,7 +42,7 @@ export type OpenTableCellArgs = {
};

export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
const { onIndexIdentifierClick } = useContext(RecordIndexRootPropsContext);
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext);
const moveEditModeToTableCellPosition =
useMoveEditModeToTableCellPosition(tableScopeId);

Expand All @@ -61,6 +62,8 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
viewableRecordNameSingularState,
);

const navigate = useNavigate();

const openTableCell = useRecoilCallback(
({ snapshot }) =>
({
Expand Down Expand Up @@ -95,7 +98,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
if (isFirstColumnCell && !isEmpty && !isActionButtonClick) {
leaveTableFocus();

onIndexIdentifierClick(recordId);
navigate(indexIdentifierUrl(recordId));

return;
}
Expand Down Expand Up @@ -143,7 +146,8 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
openRightDrawer,
setViewableRecordId,
setViewableRecordNameSingular,
onIndexIdentifierClick,
indexIdentifierUrl,
navigate,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
IconDoorEnter,
IconFunction,
IconHierarchy2,
IconKey,
IconMail,
IconRocket,
IconSettings,
IconTool,
IconUserCircle,
IconUsers,
IconKey,
MAIN_COLORS,
} from 'twenty-ui';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const RecordIndexPage = () => {
createNewTableRecord();
};

const { handleIndexIdentifierClick } = useHandleIndexIdentifierClick({
const { indexIdentifierUrl } = useHandleIndexIdentifierClick({
objectMetadataItem,
recordIndexId,
});
Expand All @@ -67,7 +67,7 @@ export const RecordIndexPage = () => {
objectNameSingular,
objectMetadataItem,
onIndexRecordsLoaded: handleIndexRecordsLoaded,
onIndexIdentifierClick: handleIndexIdentifierClick,
indexIdentifierUrl,
onCreateRecord: handleCreateRecord,
}}
>
Expand Down
27 changes: 23 additions & 4 deletions packages/twenty-ui/src/display/chip/components/AvatarChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { isDefined } from '@ui/utilities/isDefined';
import { Nullable } from '@ui/utilities/types/Nullable';
import { MouseEvent, useContext } from 'react';

// Import Link from react-router-dom instead of UndecoratedLink
import { Link } from 'react-router-dom';

export type AvatarChipProps = {
name: string;
avatarUrl?: string;
Expand All @@ -20,6 +23,7 @@ export type AvatarChipProps = {
className?: string;
placeholderColorSeed?: string;
onClick?: (event: MouseEvent) => void;
to?: string;
};

export enum AvatarChipVariant {
Expand All @@ -37,6 +41,12 @@ const StyledInvertedIconContainer = styled.div<{ backgroundColor: string }>`
background-color: ${({ backgroundColor }) => backgroundColor};
`;

// Ideally we would use the UndecoratedLink component from @ui/navigation
// but it led to a bug probably linked to circular dependencies, which was hard to solve
const StyledLink = styled(Link)`
text-decoration: none;
`;

export const AvatarChip = ({
name,
avatarUrl,
Expand All @@ -48,15 +58,16 @@ export const AvatarChip = ({
className,
placeholderColorSeed,
onClick,
to,
size = ChipSize.Small,
}: AvatarChipProps) => {
const { theme } = useContext(ThemeContext);

return (
const chip = (
<Chip
label={name}
variant={
isDefined(onClick)
isDefined(onClick) || isDefined(to)
? variant === AvatarChipVariant.Regular
? ChipVariant.Highlighted
: ChipVariant.Regular
Expand Down Expand Up @@ -92,9 +103,17 @@ export const AvatarChip = ({
/>
)
}
clickable={isDefined(onClick)}
onClick={onClick}
clickable={isDefined(onClick) || isDefined(to)}
onClick={to ? undefined : onClick}
className={className}
/>
);

return to ? (
<StyledLink to={to} onClick={onClick}>
{chip}
</StyledLink>
) : (
chip
);
};

0 comments on commit 4e59f00

Please sign in to comment.