Skip to content

Commit

Permalink
feat(PrioritySelector): new component (#1308)
Browse files Browse the repository at this point in the history
Co-authored-by: Eszter <hofmann@textkernel.nl>
Co-authored-by: Oleksii Mukiienko <mukiienko@textkernel.nl>
  • Loading branch information
3 people authored Nov 8, 2024
1 parent 9496a14 commit 5b5e09b
Show file tree
Hide file tree
Showing 16 changed files with 863 additions and 166 deletions.
88 changes: 56 additions & 32 deletions src/components/Dropdown/Items/MultiSelectItem/MultiSelectItem.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,83 @@
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
import React from 'react';
import {
DropdownMenuCheckboxItem,
DropdownMenuCheckboxItemProps,
} from '@radix-ui/react-dropdown-menu';
import { PrioritySelector, PrioritySelectorProps } from '../../../PrioritySelector';
import { bem } from '../../../../utils';
import { VisualCheckbox } from '../../../Checkbox';
import { Text } from '../../../Text';
import styles from './MultiSelectItem.scss';
import { stopPropagation } from '../../../../utils/misc';

export interface Props extends DropdownMenuCheckboxItemProps {
export interface Props<PriorityItemValue> extends DropdownMenuCheckboxItemProps {
/** Id for the checkbox */
id?: string;
/** a variant to determine the look and feel of component */
variant?: 'option' | 'select-all' | 'group-title';
/** to add a priority badge before the label */
hasPriority?: boolean;
/** props for PrioritySelector */
priority?: PrioritySelectorProps<PriorityItemValue>;
/** Checkbox status */
isSelected?: boolean;
}

const { block } = bem('MultiSelectItem', styles);

export const MultiSelectItem = React.forwardRef<HTMLLIElement, Props>(
(
export const MultiSelectItem = React.forwardRef(
<PriorityItemValue extends unknown>(
{
children,
isSelected = false,
disabled = false,
variant = 'option',
hasPriority = false, // eslint-disable-line @typescript-eslint/no-unused-vars
onCheckedChange,
priority,
...rest
},
ref
) => (
<DropdownMenuCheckboxItem
ref={ref}
role="option"
aria-selected={isSelected}
tabIndex={disabled ? -1 : 0}
checked={isSelected}
onSelect={(e) => {
e.preventDefault();
}}
onCheckedChange={onCheckedChange}
{...rest}
{...block({
isSelected,
disabled,
variant,
...rest,
})}
>
<VisualCheckbox checked={isSelected} />
<Text inline>{children}</Text>
</DropdownMenuCheckboxItem>
)
);
}: Props<PriorityItemValue>,
ref: React.Ref<HTMLDivElement>
) => {
const priorityRef = React.useRef<HTMLDivElement>(null);

const hasPriorityList = priority && priority.list.length > 0;

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Tab' && !e.shiftKey) {
if (priorityRef.current) {
priorityRef.current.focus();
}
}
};

return (
<DropdownMenuCheckboxItem
ref={ref}
role="option"
aria-selected={isSelected}
onKeyDown={handleKeyDown}
checked={isSelected}
onSelect={(e) => {
e.preventDefault();
}}
onCheckedChange={onCheckedChange}
{...rest}
{...block({
isSelected,
disabled,
variant,
...rest,
})}
>
<VisualCheckbox checked={isSelected} />
{hasPriorityList && (
<div role="none" onClick={stopPropagation} onKeyDown={stopPropagation}>
<PrioritySelector {...priority} buttonRef={priorityRef} />
</div>
)}
<Text inline>{children}</Text>
</DropdownMenuCheckboxItem>
);
}
) as <PriorityItemValue extends unknown>(
p: Props<PriorityItemValue> & { ref?: React.Ref<HTMLDivElement> }
) => React.ReactElement;
74 changes: 49 additions & 25 deletions src/components/Dropdown/Items/SingleSelectItem/SingleSelectItem.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,69 @@
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
import React from 'react';
import { DropdownMenuItem, DropdownMenuItemProps } from '@radix-ui/react-dropdown-menu';
import { PrioritySelector, PrioritySelectorProps } from '../../../PrioritySelector';
import { bem } from '../../../../utils';
import styles from '../Item.scss';
import { stopPropagation } from '../../../../utils/misc';

export interface Props extends Omit<DropdownMenuItemProps, 'onSelect'> {
export interface Props<PriorityItemValue> extends Omit<DropdownMenuItemProps, 'onSelect'> {
/** A function to be called if the item is clicked */
onSelect?: (e: React.SyntheticEvent<HTMLDivElement>) => void;
/** Id for the checkbox */
id?: string;
/** to add a priority badge before the label */
hasPriority?: boolean;
/** Checkbox status */
isSelected?: boolean;
/** props for PrioritySelector */
priority?: PrioritySelectorProps<PriorityItemValue>;
}

const { block } = bem('DropdownItem', styles);

export const SingleSelectItem = React.forwardRef<HTMLDivElement, Props>(
(
export const SingleSelectItem = React.forwardRef(
<PriorityItemValue extends unknown>(
{
children,
isSelected = false,
disabled = false,
hasPriority = false, // eslint-disable-line @typescript-eslint/no-unused-vars
priority,
...rest
},
ref
) => (
<DropdownMenuItem
ref={ref}
role="option"
aria-selected={isSelected}
tabIndex={disabled ? -1 : 0}
{...rest}
{...block({
isSelected,
disabled,
...rest,
})}
>
{children}
</DropdownMenuItem>
)
);
}: Props<PriorityItemValue>,
ref: React.Ref<HTMLDivElement>
) => {
const hasPriorityList = priority && priority.list.length > 0;
const priorityRef = React.useRef<HTMLDivElement>(null);

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Tab' && !e.shiftKey) {
if (priorityRef.current) {
priorityRef.current.focus();
}
}
};

return (
<DropdownMenuItem
ref={ref}
role="option"
aria-selected={isSelected}
tabIndex={disabled ? -1 : 0}
onKeyDown={handleKeyDown}
{...rest}
{...block({
isSelected,
disabled,
...rest,
})}
>
{hasPriorityList && (
<div role="none" onClick={stopPropagation} onKeyDown={stopPropagation}>
<PrioritySelector {...priority} buttonRef={priorityRef} />
</div>
)}
{children}
</DropdownMenuItem>
);
}
) as <PriorityItemValue extends unknown>(
p: Props<PriorityItemValue> & { ref?: React.Ref<HTMLElement> }
) => React.ReactElement;
117 changes: 117 additions & 0 deletions src/components/PrioritySelector/PrioritySelector.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
@use 'sass:map';
@mixin common-button-style(
$hoverColor: var(--color-background-neutral-subtlest-hover),
$activeColor: var(--color-background-neutral-subtlest-pressed)
) {
& {
width: var(--space-400);
height: var(--space-400);
align-items: center;
background-color: var(--transparent);
cursor: pointer;
display: flex;
flex-shrink: 0;
justify-content: center;
}
&:hover:not([disabled]) {
background-color: $hoverColor;
}
&:active:not([disabled]) {
background-color: $activeColor;
}
&[disabled] {
cursor: not-allowed;
}
}
@mixin selected-state(
$selectedColor: var(--color-background-selected-subtlest-default),
$hoverColor: var(--color-background-selected-subtlest-hover),
$activeColor: var(--color-background-selected-subtlest-pressed)
) {
& {
background-color: $selectedColor;
}
&:hover:not([disabled]) {
background-color: $hoverColor;
}
&:active:not([disabled]) {
background-color: $activeColor;
}
&[disabled] {
background-color: $selectedColor;
}
}
$icon-status-colors: (
mandatory: (
normal: var(--color-icon-success-default),
disabled: var(--color-icon-success-disabled),
),
important: (
normal: var(--color-icon-caution-default),
disabled: var(--color-icon-caution-disabled),
),
optional: (
normal: var(--color-icon-subtle),
disabled: var(--color-icon-disabled),
),
exclude: (
normal: var(--color-icon-critical-default),
disabled: var(--color-icon-critical-disabled),
),
);
.PrioritySelector {
width: 100%;
@each $status, $colors in $icon-status-colors {
&--#{$status} {
fill: map.get($colors, normal);
&[disabled] {
fill: map.get($colors, disabled);
}
}
}

&__icon {
outline: none;
min-height: 20px;
min-width: 20px;
@each $status, $colors in $icon-status-colors {
&--#{$status} {
fill: map.get($colors, normal);
&[disabled] {
fill: map.get($colors, disabled);
}
}
}

&--inList {
height: 20px;
width: 20px;
}
}
&__priorityButton--isSelected,
&__optionButton--isSelected {
@include selected-state();
}

&__badgeListItem {
display: flex;
align-items: center;
justify-content: flex-start;
gap: var(--space-100);
}
&__badgeDropdownList {
max-width: 400px;
border-radius: var(--space-100);
overflow: hidden;
background-color: white;

&--fixedWidth{
width: 232px;
}
}

}




Loading

0 comments on commit 5b5e09b

Please sign in to comment.