Skip to content

Commit 5b5e09b

Browse files
janseke10eszthoffOleksii Mukiienko
authored
feat(PrioritySelector): new component (#1308)
Co-authored-by: Eszter <hofmann@textkernel.nl> Co-authored-by: Oleksii Mukiienko <mukiienko@textkernel.nl>
1 parent 9496a14 commit 5b5e09b

File tree

16 files changed

+863
-166
lines changed

16 files changed

+863
-166
lines changed
Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,83 @@
1+
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
12
import React from 'react';
23
import {
34
DropdownMenuCheckboxItem,
45
DropdownMenuCheckboxItemProps,
56
} from '@radix-ui/react-dropdown-menu';
7+
import { PrioritySelector, PrioritySelectorProps } from '../../../PrioritySelector';
68
import { bem } from '../../../../utils';
79
import { VisualCheckbox } from '../../../Checkbox';
810
import { Text } from '../../../Text';
911
import styles from './MultiSelectItem.scss';
12+
import { stopPropagation } from '../../../../utils/misc';
1013

11-
export interface Props extends DropdownMenuCheckboxItemProps {
14+
export interface Props<PriorityItemValue> extends DropdownMenuCheckboxItemProps {
1215
/** Id for the checkbox */
1316
id?: string;
1417
/** a variant to determine the look and feel of component */
1518
variant?: 'option' | 'select-all' | 'group-title';
16-
/** to add a priority badge before the label */
17-
hasPriority?: boolean;
19+
/** props for PrioritySelector */
20+
priority?: PrioritySelectorProps<PriorityItemValue>;
1821
/** Checkbox status */
1922
isSelected?: boolean;
2023
}
2124

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

24-
export const MultiSelectItem = React.forwardRef<HTMLLIElement, Props>(
25-
(
27+
export const MultiSelectItem = React.forwardRef(
28+
<PriorityItemValue extends unknown>(
2629
{
2730
children,
2831
isSelected = false,
2932
disabled = false,
3033
variant = 'option',
31-
hasPriority = false, // eslint-disable-line @typescript-eslint/no-unused-vars
3234
onCheckedChange,
35+
priority,
3336
...rest
34-
},
35-
ref
36-
) => (
37-
<DropdownMenuCheckboxItem
38-
ref={ref}
39-
role="option"
40-
aria-selected={isSelected}
41-
tabIndex={disabled ? -1 : 0}
42-
checked={isSelected}
43-
onSelect={(e) => {
44-
e.preventDefault();
45-
}}
46-
onCheckedChange={onCheckedChange}
47-
{...rest}
48-
{...block({
49-
isSelected,
50-
disabled,
51-
variant,
52-
...rest,
53-
})}
54-
>
55-
<VisualCheckbox checked={isSelected} />
56-
<Text inline>{children}</Text>
57-
</DropdownMenuCheckboxItem>
58-
)
59-
);
37+
}: Props<PriorityItemValue>,
38+
ref: React.Ref<HTMLDivElement>
39+
) => {
40+
const priorityRef = React.useRef<HTMLDivElement>(null);
41+
42+
const hasPriorityList = priority && priority.list.length > 0;
43+
44+
const handleKeyDown = (e: React.KeyboardEvent) => {
45+
if (e.key === 'Tab' && !e.shiftKey) {
46+
if (priorityRef.current) {
47+
priorityRef.current.focus();
48+
}
49+
}
50+
};
51+
52+
return (
53+
<DropdownMenuCheckboxItem
54+
ref={ref}
55+
role="option"
56+
aria-selected={isSelected}
57+
onKeyDown={handleKeyDown}
58+
checked={isSelected}
59+
onSelect={(e) => {
60+
e.preventDefault();
61+
}}
62+
onCheckedChange={onCheckedChange}
63+
{...rest}
64+
{...block({
65+
isSelected,
66+
disabled,
67+
variant,
68+
...rest,
69+
})}
70+
>
71+
<VisualCheckbox checked={isSelected} />
72+
{hasPriorityList && (
73+
<div role="none" onClick={stopPropagation} onKeyDown={stopPropagation}>
74+
<PrioritySelector {...priority} buttonRef={priorityRef} />
75+
</div>
76+
)}
77+
<Text inline>{children}</Text>
78+
</DropdownMenuCheckboxItem>
79+
);
80+
}
81+
) as <PriorityItemValue extends unknown>(
82+
p: Props<PriorityItemValue> & { ref?: React.Ref<HTMLDivElement> }
83+
) => React.ReactElement;
Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,69 @@
1+
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
12
import React from 'react';
23
import { DropdownMenuItem, DropdownMenuItemProps } from '@radix-ui/react-dropdown-menu';
4+
import { PrioritySelector, PrioritySelectorProps } from '../../../PrioritySelector';
35
import { bem } from '../../../../utils';
46
import styles from '../Item.scss';
7+
import { stopPropagation } from '../../../../utils/misc';
58

6-
export interface Props extends Omit<DropdownMenuItemProps, 'onSelect'> {
9+
export interface Props<PriorityItemValue> extends Omit<DropdownMenuItemProps, 'onSelect'> {
710
/** A function to be called if the item is clicked */
811
onSelect?: (e: React.SyntheticEvent<HTMLDivElement>) => void;
912
/** Id for the checkbox */
1013
id?: string;
11-
/** to add a priority badge before the label */
12-
hasPriority?: boolean;
1314
/** Checkbox status */
1415
isSelected?: boolean;
16+
/** props for PrioritySelector */
17+
priority?: PrioritySelectorProps<PriorityItemValue>;
1518
}
1619

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

19-
export const SingleSelectItem = React.forwardRef<HTMLDivElement, Props>(
20-
(
22+
export const SingleSelectItem = React.forwardRef(
23+
<PriorityItemValue extends unknown>(
2124
{
2225
children,
2326
isSelected = false,
2427
disabled = false,
25-
hasPriority = false, // eslint-disable-line @typescript-eslint/no-unused-vars
28+
priority,
2629
...rest
27-
},
28-
ref
29-
) => (
30-
<DropdownMenuItem
31-
ref={ref}
32-
role="option"
33-
aria-selected={isSelected}
34-
tabIndex={disabled ? -1 : 0}
35-
{...rest}
36-
{...block({
37-
isSelected,
38-
disabled,
39-
...rest,
40-
})}
41-
>
42-
{children}
43-
</DropdownMenuItem>
44-
)
45-
);
30+
}: Props<PriorityItemValue>,
31+
ref: React.Ref<HTMLDivElement>
32+
) => {
33+
const hasPriorityList = priority && priority.list.length > 0;
34+
const priorityRef = React.useRef<HTMLDivElement>(null);
35+
36+
const handleKeyDown = (e: React.KeyboardEvent) => {
37+
if (e.key === 'Tab' && !e.shiftKey) {
38+
if (priorityRef.current) {
39+
priorityRef.current.focus();
40+
}
41+
}
42+
};
43+
44+
return (
45+
<DropdownMenuItem
46+
ref={ref}
47+
role="option"
48+
aria-selected={isSelected}
49+
tabIndex={disabled ? -1 : 0}
50+
onKeyDown={handleKeyDown}
51+
{...rest}
52+
{...block({
53+
isSelected,
54+
disabled,
55+
...rest,
56+
})}
57+
>
58+
{hasPriorityList && (
59+
<div role="none" onClick={stopPropagation} onKeyDown={stopPropagation}>
60+
<PrioritySelector {...priority} buttonRef={priorityRef} />
61+
</div>
62+
)}
63+
{children}
64+
</DropdownMenuItem>
65+
);
66+
}
67+
) as <PriorityItemValue extends unknown>(
68+
p: Props<PriorityItemValue> & { ref?: React.Ref<HTMLElement> }
69+
) => React.ReactElement;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
@use 'sass:map';
2+
@mixin common-button-style(
3+
$hoverColor: var(--color-background-neutral-subtlest-hover),
4+
$activeColor: var(--color-background-neutral-subtlest-pressed)
5+
) {
6+
& {
7+
width: var(--space-400);
8+
height: var(--space-400);
9+
align-items: center;
10+
background-color: var(--transparent);
11+
cursor: pointer;
12+
display: flex;
13+
flex-shrink: 0;
14+
justify-content: center;
15+
}
16+
&:hover:not([disabled]) {
17+
background-color: $hoverColor;
18+
}
19+
&:active:not([disabled]) {
20+
background-color: $activeColor;
21+
}
22+
&[disabled] {
23+
cursor: not-allowed;
24+
}
25+
}
26+
@mixin selected-state(
27+
$selectedColor: var(--color-background-selected-subtlest-default),
28+
$hoverColor: var(--color-background-selected-subtlest-hover),
29+
$activeColor: var(--color-background-selected-subtlest-pressed)
30+
) {
31+
& {
32+
background-color: $selectedColor;
33+
}
34+
&:hover:not([disabled]) {
35+
background-color: $hoverColor;
36+
}
37+
&:active:not([disabled]) {
38+
background-color: $activeColor;
39+
}
40+
&[disabled] {
41+
background-color: $selectedColor;
42+
}
43+
}
44+
$icon-status-colors: (
45+
mandatory: (
46+
normal: var(--color-icon-success-default),
47+
disabled: var(--color-icon-success-disabled),
48+
),
49+
important: (
50+
normal: var(--color-icon-caution-default),
51+
disabled: var(--color-icon-caution-disabled),
52+
),
53+
optional: (
54+
normal: var(--color-icon-subtle),
55+
disabled: var(--color-icon-disabled),
56+
),
57+
exclude: (
58+
normal: var(--color-icon-critical-default),
59+
disabled: var(--color-icon-critical-disabled),
60+
),
61+
);
62+
.PrioritySelector {
63+
width: 100%;
64+
@each $status, $colors in $icon-status-colors {
65+
&--#{$status} {
66+
fill: map.get($colors, normal);
67+
&[disabled] {
68+
fill: map.get($colors, disabled);
69+
}
70+
}
71+
}
72+
73+
&__icon {
74+
outline: none;
75+
min-height: 20px;
76+
min-width: 20px;
77+
@each $status, $colors in $icon-status-colors {
78+
&--#{$status} {
79+
fill: map.get($colors, normal);
80+
&[disabled] {
81+
fill: map.get($colors, disabled);
82+
}
83+
}
84+
}
85+
86+
&--inList {
87+
height: 20px;
88+
width: 20px;
89+
}
90+
}
91+
&__priorityButton--isSelected,
92+
&__optionButton--isSelected {
93+
@include selected-state();
94+
}
95+
96+
&__badgeListItem {
97+
display: flex;
98+
align-items: center;
99+
justify-content: flex-start;
100+
gap: var(--space-100);
101+
}
102+
&__badgeDropdownList {
103+
max-width: 400px;
104+
border-radius: var(--space-100);
105+
overflow: hidden;
106+
background-color: white;
107+
108+
&--fixedWidth{
109+
width: 232px;
110+
}
111+
}
112+
113+
}
114+
115+
116+
117+

0 commit comments

Comments
 (0)