@apify/ui-library 1.141.3 → 1.145.2-featverifieddeveloperbadge-bd59c6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -19
- package/dist/src/components/box/box.d.ts +15 -14
- package/dist/src/components/box/box.d.ts.map +1 -1
- package/dist/src/components/box/box.js +10 -33
- package/dist/src/components/box/box.js.map +1 -1
- package/dist/src/components/breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/src/components/breadcrumb/breadcrumb.js +1 -0
- package/dist/src/components/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/src/components/card/card.d.ts.map +1 -1
- package/dist/src/components/card/card.js +1 -0
- package/dist/src/components/card/card.js.map +1 -1
- package/dist/src/components/chip/chip.d.ts.map +1 -1
- package/dist/src/components/chip/chip.js +2 -0
- package/dist/src/components/chip/chip.js.map +1 -1
- package/dist/src/components/code/code_block/code_block.styled.d.ts.map +1 -1
- package/dist/src/components/code/code_block/code_block.styled.js +1 -2
- package/dist/src/components/code/code_block/code_block.styled.js.map +1 -1
- package/dist/src/components/code/one_light_theme.d.ts.map +1 -1
- package/dist/src/components/code/one_light_theme.js +1 -0
- package/dist/src/components/code/one_light_theme.js.map +1 -1
- package/dist/src/components/dropdown/dropdown.context.d.ts +6 -0
- package/dist/src/components/dropdown/dropdown.context.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.context.js +10 -0
- package/dist/src/components/dropdown/dropdown.context.js.map +1 -0
- package/dist/src/components/dropdown/dropdown.d.ts +48 -0
- package/dist/src/components/dropdown/dropdown.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.js +65 -0
- package/dist/src/components/dropdown/dropdown.js.map +1 -0
- package/dist/src/components/dropdown/dropdown.styled.d.ts +35 -0
- package/dist/src/components/dropdown/dropdown.styled.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.styled.js +241 -0
- package/dist/src/components/dropdown/dropdown.styled.js.map +1 -0
- package/dist/src/components/dropdown/dropdown.types.d.ts +17 -0
- package/dist/src/components/dropdown/dropdown.types.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.types.js +2 -0
- package/dist/src/components/dropdown/dropdown.types.js.map +1 -0
- package/dist/src/components/dropdown/dropdown_button.d.ts +10 -0
- package/dist/src/components/dropdown/dropdown_button.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown_button.js +16 -0
- package/dist/src/components/dropdown/dropdown_button.js.map +1 -0
- package/dist/src/components/dropdown/dropdown_root.d.ts +11 -0
- package/dist/src/components/dropdown/dropdown_root.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown_root.js +19 -0
- package/dist/src/components/dropdown/dropdown_root.js.map +1 -0
- package/dist/src/components/dropdown/dropdown_shell.d.ts +15 -0
- package/dist/src/components/dropdown/dropdown_shell.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown_shell.js +69 -0
- package/dist/src/components/dropdown/dropdown_shell.js.map +1 -0
- package/dist/src/components/dropdown/index.d.ts +7 -0
- package/dist/src/components/dropdown/index.d.ts.map +1 -0
- package/dist/src/components/dropdown/index.js +5 -0
- package/dist/src/components/dropdown/index.js.map +1 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +1 -0
- package/dist/src/components/index.js.map +1 -1
- package/dist/src/components/message/message.d.ts +2 -0
- package/dist/src/components/message/message.d.ts.map +1 -1
- package/dist/src/components/message/message.js +2 -0
- package/dist/src/components/message/message.js.map +1 -1
- package/dist/src/components/pagination/pagination.d.ts.map +1 -1
- package/dist/src/components/pagination/pagination.js +1 -0
- package/dist/src/components/pagination/pagination.js.map +1 -1
- package/dist/src/components/tabs/tab.js +1 -1
- package/dist/src/components/tabs/tab.js.map +1 -1
- package/dist/src/utils/css_utils.d.ts +17 -0
- package/dist/src/utils/css_utils.d.ts.map +1 -1
- package/dist/src/utils/css_utils.js +54 -0
- package/dist/src/utils/css_utils.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -5
- package/src/components/box/box.stories.tsx +18 -0
- package/src/components/box/box.tsx +31 -62
- package/src/components/breadcrumb/breadcrumb.tsx +1 -0
- package/src/components/card/card.tsx +1 -0
- package/src/components/chip/chip.tsx +2 -0
- package/src/components/code/code_block/code_block.styled.tsx +1 -2
- package/src/components/code/one_light_theme.ts +1 -0
- package/src/components/dropdown/dropdown.context.tsx +14 -0
- package/src/components/dropdown/dropdown.stories.tsx +343 -0
- package/src/components/dropdown/dropdown.styled.ts +265 -0
- package/src/components/dropdown/dropdown.tsx +259 -0
- package/src/components/dropdown/dropdown.types.ts +18 -0
- package/src/components/dropdown/dropdown_button.tsx +45 -0
- package/src/components/dropdown/dropdown_root.tsx +41 -0
- package/src/components/dropdown/dropdown_shell.tsx +139 -0
- package/src/components/dropdown/index.ts +6 -0
- package/src/components/index.ts +1 -0
- package/src/components/message/message.stories.jsx +1 -0
- package/src/components/message/message.tsx +2 -0
- package/src/components/pagination/pagination.tsx +1 -0
- package/src/components/tabs/tab.tsx +1 -1
- package/src/design_system/colors/build_color_tokens.js +14 -0
- package/src/design_system/colors/colors.stories.tsx +395 -0
- package/src/utils/css_utils.ts +79 -0
- package/style/colors/tokens.dark.css +149 -0
- package/style/colors/tokens.light.css +149 -0
- package/src/design_system/colors/Colors.mdx +0 -50
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import { theme } from '../../design_system/theme.js';
|
|
5
|
+
|
|
6
|
+
export const DROPDOWN_ELEMENT_CLASSES = {
|
|
7
|
+
WRAPPER: 'dropdown-wrapper',
|
|
8
|
+
ITEM: 'dropdown-item',
|
|
9
|
+
TRIGGER_OVERLAY: 'dropdown-trigger-overlay',
|
|
10
|
+
RADIO_OPTION: 'dropdown-radio-option',
|
|
11
|
+
RADIO_OPTION_DISABLED: 'dropdown-radio-option-disabled',
|
|
12
|
+
RADIO_OPTION_SELECTED: 'dropdown-radio-option-selected',
|
|
13
|
+
RADIO_OPTION_ICON: 'dropdown-radio-option-icon',
|
|
14
|
+
RADIO_OPTION_CONTENT: 'dropdown-radio-option-content',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type StyledContentProps = {
|
|
18
|
+
$width?: string;
|
|
19
|
+
$height?: string;
|
|
20
|
+
$zIndex?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const StyledContent = styled(DropdownMenu.Content).attrs<
|
|
24
|
+
DropdownMenu.DropdownMenuContentProps & { 'data-test'?: string }
|
|
25
|
+
>((props) => ({
|
|
26
|
+
'data-test': 'dropdown-wrapper',
|
|
27
|
+
className: [DROPDOWN_ELEMENT_CLASSES.WRAPPER, props.className].filter(Boolean).join(' '),
|
|
28
|
+
}))<StyledContentProps>`
|
|
29
|
+
background: ${theme.color.neutral.background};
|
|
30
|
+
border: 1px solid ${theme.color.neutral.separatorSubtle};
|
|
31
|
+
border-radius: ${theme.radius.radius12};
|
|
32
|
+
padding: ${theme.space.space6} 0;
|
|
33
|
+
min-width: 80px;
|
|
34
|
+
width: ${(props) => props.$width || 'auto'};
|
|
35
|
+
height: ${(props) => props.$height || 'auto'};
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
z-index: ${(props) => props.$zIndex ?? 100} !important;
|
|
40
|
+
position: relative;
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
export const ScrollableContent = styled.div`
|
|
44
|
+
flex: 1;
|
|
45
|
+
overflow-y: auto;
|
|
46
|
+
overflow-x: hidden;
|
|
47
|
+
gap: ${theme.space.space4};
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
scrollbar-width: thin;
|
|
51
|
+
scrollbar-color: ${theme.color.neutral.separatorSubtle} transparent;
|
|
52
|
+
|
|
53
|
+
.${DROPDOWN_ELEMENT_CLASSES.TRIGGER_OVERLAY} {
|
|
54
|
+
position: absolute;
|
|
55
|
+
top: 0;
|
|
56
|
+
left: 0;
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
z-index: 1;
|
|
61
|
+
pointer-events: none;
|
|
62
|
+
}
|
|
63
|
+
.${DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION} {
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
width: 100%;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: flex-start;
|
|
68
|
+
padding: ${theme.space.space4} ${theme.space.space8};
|
|
69
|
+
border-radius: ${theme.radius.radius4};
|
|
70
|
+
gap: ${theme.space.space8};
|
|
71
|
+
min-height: 28px;
|
|
72
|
+
|
|
73
|
+
&:hover {
|
|
74
|
+
background: ${theme.color.neutral.hover};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
.${DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_DISABLED} {
|
|
78
|
+
cursor: not-allowed;
|
|
79
|
+
color: ${theme.color.neutral.textDisabled};
|
|
80
|
+
|
|
81
|
+
* {
|
|
82
|
+
color: ${theme.color.neutral.textDisabled} !important;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
.${DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_SELECTED} {
|
|
86
|
+
background: none;
|
|
87
|
+
color: ${theme.color.neutral.text};
|
|
88
|
+
}
|
|
89
|
+
.${DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_ICON} {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: flex-start;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
padding: ${theme.space.space2} 0;
|
|
94
|
+
min-width: 16px;
|
|
95
|
+
min-height: 20px;
|
|
96
|
+
margin-right: ${theme.space.space4};
|
|
97
|
+
}
|
|
98
|
+
.${DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_CONTENT} {
|
|
99
|
+
flex: 1;
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
min-width: 0;
|
|
103
|
+
max-width: 100%;
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
export const StyledItemRow = styled.div`
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: flex-start;
|
|
110
|
+
width: 100%;
|
|
111
|
+
gap: ${theme.space.space4};
|
|
112
|
+
min-height: 28px;
|
|
113
|
+
padding: ${theme.space.space4} 0;
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
export const StyledItemWrapper = styled.div`
|
|
117
|
+
margin: 0 ${theme.space.space6};
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
export const StyledItem = styled(DropdownMenu.Item)`
|
|
121
|
+
min-width: 100%;
|
|
122
|
+
display: block;
|
|
123
|
+
padding: 0 ${theme.space.space8};
|
|
124
|
+
color: ${theme.color.neutral.text};
|
|
125
|
+
border-radius: ${theme.radius.radius6};
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
background: none;
|
|
128
|
+
border: none;
|
|
129
|
+
transition: background 0.15s;
|
|
130
|
+
outline: none;
|
|
131
|
+
box-shadow: none;
|
|
132
|
+
|
|
133
|
+
&[data-highlighted],
|
|
134
|
+
&.dropdown-item-selected {
|
|
135
|
+
background: ${theme.color.neutral.hover};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&[data-disabled] {
|
|
139
|
+
color: ${theme.color.neutral.textDisabled};
|
|
140
|
+
cursor: not-allowed;
|
|
141
|
+
}
|
|
142
|
+
&.no-hover {
|
|
143
|
+
cursor: default;
|
|
144
|
+
&[data-highlighted],
|
|
145
|
+
&.dropdown-item-selected,
|
|
146
|
+
&:hover {
|
|
147
|
+
background: none !important;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
export const StyledCheckboxItem = styled(DropdownMenu.CheckboxItem)`
|
|
153
|
+
min-width: 100%;
|
|
154
|
+
display: block;
|
|
155
|
+
padding: 0 ${theme.space.space8};
|
|
156
|
+
color: ${theme.color.neutral.text};
|
|
157
|
+
border-radius: ${theme.radius.radius4};
|
|
158
|
+
cursor: pointer;
|
|
159
|
+
background: none;
|
|
160
|
+
border: none;
|
|
161
|
+
transition: background 0.15s;
|
|
162
|
+
outline: none;
|
|
163
|
+
box-shadow: none;
|
|
164
|
+
|
|
165
|
+
&[data-highlighted],
|
|
166
|
+
&:hover {
|
|
167
|
+
background: ${theme.color.neutral.hover};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&[data-disabled] {
|
|
171
|
+
color: ${theme.color.neutral.textDisabled};
|
|
172
|
+
cursor: not-allowed;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&[data-state='checked'] {
|
|
176
|
+
background: none;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
&.no-hover {
|
|
180
|
+
cursor: default;
|
|
181
|
+
|
|
182
|
+
&[data-highlighted],
|
|
183
|
+
&:hover {
|
|
184
|
+
background: none !important;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
|
|
189
|
+
export const StyledLabel = styled(DropdownMenu.Label)`
|
|
190
|
+
padding: ${theme.space.space8};
|
|
191
|
+
padding-bottom: ${theme.space.space2};
|
|
192
|
+
color: ${theme.color.neutral.textSubtle};
|
|
193
|
+
${theme.typography.shared.desktop.bodyS}
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
gap: ${theme.space.space4};
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
export const StyledDescription = styled.div`
|
|
200
|
+
color: ${theme.color.neutral.textSubtle};
|
|
201
|
+
${theme.typography.shared.desktop.bodyS}
|
|
202
|
+
display: block;
|
|
203
|
+
width: 100%;
|
|
204
|
+
margin-top: ${theme.space.space2};
|
|
205
|
+
|
|
206
|
+
[data-disabled] & {
|
|
207
|
+
color: ${theme.color.neutral.textDisabled};
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
export const StyledItemTitle = styled.span`
|
|
212
|
+
${theme.typography.shared.desktop.bodyM}
|
|
213
|
+
color: ${theme.color.neutral.textMuted};
|
|
214
|
+
display: block;
|
|
215
|
+
width: 100%;
|
|
216
|
+
margin-right: ${theme.space.space32};
|
|
217
|
+
|
|
218
|
+
[data-disabled] & {
|
|
219
|
+
color: ${theme.color.neutral.textDisabled};
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
export const StyledIconWrapper = styled.span`
|
|
224
|
+
display: flex;
|
|
225
|
+
justify-content: center;
|
|
226
|
+
align-items: flex-start;
|
|
227
|
+
flex-shrink: 0;
|
|
228
|
+
width: 20px;
|
|
229
|
+
padding-top: ${theme.space.space2};
|
|
230
|
+
color: ${theme.color.neutral.textMuted};
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
export const StyledCheckboxIconWrapper = styled(StyledIconWrapper)`
|
|
234
|
+
margin-right: ${theme.space.space4};
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
export const StyledContentWrapper = styled.div`
|
|
238
|
+
flex: 1;
|
|
239
|
+
display: flex;
|
|
240
|
+
flex-direction: column;
|
|
241
|
+
align-items: flex-start;
|
|
242
|
+
min-width: 0;
|
|
243
|
+
width: max-content;
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
export const StyledTagWrapper = styled.div`
|
|
248
|
+
display: flex;
|
|
249
|
+
gap: ${theme.space.space4};
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
export const StyledControls = styled.div`
|
|
253
|
+
padding-top: ${theme.space.space6};
|
|
254
|
+
border-top: 1px solid ${theme.color.neutral.separatorSubtle};
|
|
255
|
+
gap: ${theme.space.space4};
|
|
256
|
+
display: flex;
|
|
257
|
+
flex-direction: column;
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
export const StyledSeparator = styled(DropdownMenu.Separator)`
|
|
261
|
+
height: 1px;
|
|
262
|
+
min-height: 1px;
|
|
263
|
+
margin: ${theme.space.space4} ${theme.space.space8};
|
|
264
|
+
background-color: ${theme.color.neutral.separatorSubtle};
|
|
265
|
+
`;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import styled from 'styled-components';
|
|
6
|
+
|
|
7
|
+
import { CheckIcon } from '@apify/ui-icons';
|
|
8
|
+
|
|
9
|
+
import { theme } from '../../design_system/theme.js';
|
|
10
|
+
import { CheckboxPrimitive } from '../checkbox/checkbox.js';
|
|
11
|
+
import { radioButtonStyle } from '../radio_button/radio_button.style.js';
|
|
12
|
+
import { useDropdownContext } from './dropdown.context.js';
|
|
13
|
+
import {
|
|
14
|
+
DROPDOWN_ELEMENT_CLASSES,
|
|
15
|
+
StyledCheckboxIconWrapper,
|
|
16
|
+
StyledCheckboxItem,
|
|
17
|
+
StyledContentWrapper,
|
|
18
|
+
StyledControls,
|
|
19
|
+
StyledDescription,
|
|
20
|
+
StyledIconWrapper,
|
|
21
|
+
StyledItem,
|
|
22
|
+
StyledItemRow,
|
|
23
|
+
StyledItemTitle,
|
|
24
|
+
StyledItemWrapper,
|
|
25
|
+
StyledLabel,
|
|
26
|
+
StyledSeparator,
|
|
27
|
+
StyledTagWrapper,
|
|
28
|
+
} from './dropdown.styled.js';
|
|
29
|
+
import type { BaseDropdownItemProps } from './dropdown.types.js';
|
|
30
|
+
import { DropdownRoot } from './dropdown_root.js';
|
|
31
|
+
|
|
32
|
+
type DropdownItemProps = React.ComponentPropsWithRef<typeof DropdownMenu.Item> &
|
|
33
|
+
BaseDropdownItemProps & {
|
|
34
|
+
trailingIcon?: React.ReactNode;
|
|
35
|
+
selected?: boolean;
|
|
36
|
+
onSelect?: (event: Event) => void;
|
|
37
|
+
tags?: React.ReactNode[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const DropdownItem = ({
|
|
41
|
+
children,
|
|
42
|
+
icon,
|
|
43
|
+
trailingIcon,
|
|
44
|
+
selected,
|
|
45
|
+
description,
|
|
46
|
+
onSelect,
|
|
47
|
+
className,
|
|
48
|
+
disabled,
|
|
49
|
+
tags = [],
|
|
50
|
+
...props
|
|
51
|
+
}: DropdownItemProps) => {
|
|
52
|
+
const { handleSelectAndClose } = useDropdownContext();
|
|
53
|
+
const handleSelect = (event: Event) => {
|
|
54
|
+
if (onSelect) onSelect(event);
|
|
55
|
+
handleSelectAndClose(event);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<StyledItemWrapper>
|
|
60
|
+
<StyledItem
|
|
61
|
+
onSelect={handleSelect}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
{...props}
|
|
64
|
+
className={clsx(DROPDOWN_ELEMENT_CLASSES.ITEM, className, selected && 'dropdown-item-selected')}
|
|
65
|
+
data-test={disabled ? 'dropdown-item-disabled' : 'dropdown-item'}
|
|
66
|
+
>
|
|
67
|
+
<StyledItemRow>
|
|
68
|
+
{icon && <StyledIconWrapper>{icon}</StyledIconWrapper>}
|
|
69
|
+
<StyledContentWrapper>
|
|
70
|
+
<StyledItemTitle>{children}</StyledItemTitle>
|
|
71
|
+
{description && <StyledDescription>{description}</StyledDescription>}
|
|
72
|
+
</StyledContentWrapper>
|
|
73
|
+
{(trailingIcon || selected) && (
|
|
74
|
+
<StyledIconWrapper>
|
|
75
|
+
{trailingIcon}
|
|
76
|
+
{!trailingIcon && selected && <CheckIcon size="16" color={theme.color.neutral.text} />}
|
|
77
|
+
</StyledIconWrapper>
|
|
78
|
+
)}
|
|
79
|
+
{tags.length > 0 && (
|
|
80
|
+
<StyledTagWrapper onClick={(e) => e.stopPropagation()}>{tags}</StyledTagWrapper>
|
|
81
|
+
)}
|
|
82
|
+
</StyledItemRow>
|
|
83
|
+
</StyledItem>
|
|
84
|
+
</StyledItemWrapper>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type DropdownCheckboxItemProps = React.ComponentPropsWithRef<typeof DropdownMenu.CheckboxItem> &
|
|
89
|
+
BaseDropdownItemProps & {
|
|
90
|
+
checked?: boolean;
|
|
91
|
+
trailingIcon?: React.ReactNode;
|
|
92
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
93
|
+
onSelect?: (event: Event) => void;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const DropdownCheckboxItem = ({
|
|
97
|
+
checked,
|
|
98
|
+
children,
|
|
99
|
+
icon,
|
|
100
|
+
trailingIcon,
|
|
101
|
+
onCheckedChange,
|
|
102
|
+
description,
|
|
103
|
+
onSelect,
|
|
104
|
+
disabled,
|
|
105
|
+
className,
|
|
106
|
+
...props
|
|
107
|
+
}: DropdownCheckboxItemProps) => {
|
|
108
|
+
const { handleSelectAndClose } = useDropdownContext();
|
|
109
|
+
const handleSelect = (event: Event) => {
|
|
110
|
+
if (disabled) return;
|
|
111
|
+
if (onSelect) onSelect(event);
|
|
112
|
+
handleSelectAndClose(event);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<StyledItemWrapper>
|
|
117
|
+
<StyledCheckboxItem
|
|
118
|
+
checked={checked}
|
|
119
|
+
onSelect={handleSelect}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
{...props}
|
|
122
|
+
onCheckedChange={disabled ? undefined : onCheckedChange}
|
|
123
|
+
className={clsx(className)}
|
|
124
|
+
data-test={disabled ? 'dropdown-item-disabled' : 'dropdown-item'}
|
|
125
|
+
>
|
|
126
|
+
<StyledItemRow>
|
|
127
|
+
<StyledCheckboxIconWrapper>
|
|
128
|
+
<CheckboxPrimitive
|
|
129
|
+
value={!!checked}
|
|
130
|
+
setValue={disabled ? () => undefined : onCheckedChange || (() => undefined)}
|
|
131
|
+
readOnly={disabled || !onCheckedChange}
|
|
132
|
+
/>
|
|
133
|
+
</StyledCheckboxIconWrapper>
|
|
134
|
+
{icon && <StyledIconWrapper>{icon}</StyledIconWrapper>}
|
|
135
|
+
<StyledContentWrapper>
|
|
136
|
+
<StyledItemTitle>{children}</StyledItemTitle>
|
|
137
|
+
{description && <StyledDescription>{description}</StyledDescription>}
|
|
138
|
+
</StyledContentWrapper>
|
|
139
|
+
{trailingIcon && <StyledIconWrapper>{trailingIcon}</StyledIconWrapper>}
|
|
140
|
+
</StyledItemRow>
|
|
141
|
+
</StyledCheckboxItem>
|
|
142
|
+
</StyledItemWrapper>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// --- Radio group: self-contained, uses radioButtonStyle from radio_button component ---
|
|
147
|
+
|
|
148
|
+
const StyledRadioGroupRoot = styled(RadioGroupPrimitive.Root)`
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const StyledRadioGroupItem = styled(RadioGroupPrimitive.Item)`
|
|
154
|
+
${radioButtonStyle}
|
|
155
|
+
flex-shrink: 0;
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
type RadioOption = {
|
|
159
|
+
label: string;
|
|
160
|
+
description?: string;
|
|
161
|
+
disabled?: boolean;
|
|
162
|
+
value: string;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const DropdownRadioOptionLabel = ({ label, description }: { label: string; description?: string }) => (
|
|
166
|
+
<>
|
|
167
|
+
<StyledItemTitle>{label}</StyledItemTitle>
|
|
168
|
+
{description && <StyledDescription title={description}>{description}</StyledDescription>}
|
|
169
|
+
</>
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
type DropdownRadioOptionProps = {
|
|
173
|
+
option: RadioOption;
|
|
174
|
+
isSelected: boolean;
|
|
175
|
+
onSelect?: () => void;
|
|
176
|
+
children: React.ReactNode;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const DropdownRadioOption = ({ option, isSelected, onSelect, children }: DropdownRadioOptionProps) => (
|
|
180
|
+
<div
|
|
181
|
+
className={clsx(
|
|
182
|
+
DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION,
|
|
183
|
+
option.disabled && DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_DISABLED,
|
|
184
|
+
isSelected && DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_SELECTED,
|
|
185
|
+
)}
|
|
186
|
+
onClick={onSelect}
|
|
187
|
+
aria-disabled={option.disabled}
|
|
188
|
+
>
|
|
189
|
+
{children}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
type DropdownRadioGroupProps = {
|
|
194
|
+
options: RadioOption[];
|
|
195
|
+
value: string;
|
|
196
|
+
setValue: (value: string) => void;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const DropdownRadioGroup = ({ options, value, setValue }: DropdownRadioGroupProps) => {
|
|
200
|
+
const { handleSelectAndClose } = useDropdownContext();
|
|
201
|
+
|
|
202
|
+
const handleSelect = (newValue: string) => {
|
|
203
|
+
setValue(newValue);
|
|
204
|
+
handleSelectAndClose();
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<StyledItemWrapper>
|
|
209
|
+
<StyledRadioGroupRoot value={value} onValueChange={handleSelect}>
|
|
210
|
+
{options.map((option) => (
|
|
211
|
+
<DropdownRadioOption
|
|
212
|
+
key={option.value}
|
|
213
|
+
option={option}
|
|
214
|
+
isSelected={option.value === value}
|
|
215
|
+
onSelect={option.disabled ? undefined : () => handleSelect(option.value)}
|
|
216
|
+
>
|
|
217
|
+
<span className={DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_ICON}>
|
|
218
|
+
<StyledRadioGroupItem
|
|
219
|
+
value={option.value}
|
|
220
|
+
id={option.value}
|
|
221
|
+
disabled={option.disabled}
|
|
222
|
+
onClick={(e) => e.stopPropagation()}
|
|
223
|
+
/>
|
|
224
|
+
</span>
|
|
225
|
+
<span className={DROPDOWN_ELEMENT_CLASSES.RADIO_OPTION_CONTENT}>
|
|
226
|
+
<DropdownRadioOptionLabel label={option.label} description={option.description} />
|
|
227
|
+
</span>
|
|
228
|
+
</DropdownRadioOption>
|
|
229
|
+
))}
|
|
230
|
+
</StyledRadioGroupRoot>
|
|
231
|
+
</StyledItemWrapper>
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
type DropdownLabelProps = React.ComponentPropsWithRef<typeof DropdownMenu.Label> & {
|
|
236
|
+
children?: React.ReactNode | React.ReactNode[];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const DropdownLabel = (props: DropdownLabelProps) => <StyledLabel {...props} />;
|
|
240
|
+
|
|
241
|
+
type DropdownControlProps = React.ComponentPropsWithRef<typeof DropdownMenu.Label> & {
|
|
242
|
+
children?: React.ReactNode | React.ReactNode[];
|
|
243
|
+
};
|
|
244
|
+
const DropdownControls = (props: DropdownControlProps) => {
|
|
245
|
+
return <StyledControls>{props.children}</StyledControls>;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const DropdownSeparator = (props: React.ComponentPropsWithRef<typeof DropdownMenu.Separator>) => (
|
|
249
|
+
<StyledSeparator {...props} />
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
export const Dropdown = Object.assign(DropdownRoot, {
|
|
253
|
+
Item: DropdownItem,
|
|
254
|
+
CheckboxItem: DropdownCheckboxItem,
|
|
255
|
+
Label: DropdownLabel,
|
|
256
|
+
RadioGroup: DropdownRadioGroup,
|
|
257
|
+
Controls: DropdownControls,
|
|
258
|
+
Separator: DropdownSeparator,
|
|
259
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import type * as React from 'react';
|
|
3
|
+
|
|
4
|
+
export type BaseDropdownItemProps = {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
icon?: React.ReactNode;
|
|
7
|
+
description?: React.ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type BaseDropdownContentProps = {
|
|
13
|
+
width?: string;
|
|
14
|
+
height?: string;
|
|
15
|
+
closeOnSelect?: boolean;
|
|
16
|
+
zIndex?: number;
|
|
17
|
+
contentProps?: React.ComponentPropsWithRef<typeof DropdownMenu.Content>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../button/button.js';
|
|
5
|
+
import { ScrollableContent, StyledContent } from './dropdown.styled.js';
|
|
6
|
+
import type { BaseDropdownContentProps } from './dropdown.types.js';
|
|
7
|
+
import { DropdownRoot } from './dropdown_root.js';
|
|
8
|
+
|
|
9
|
+
type DropdownButtonProps = BaseDropdownContentProps & {
|
|
10
|
+
buttonProps?: Partial<React.ComponentPropsWithoutRef<typeof Button>>;
|
|
11
|
+
buttonLabel: React.ReactNode;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const DropdownButton = ({
|
|
16
|
+
buttonProps,
|
|
17
|
+
buttonLabel,
|
|
18
|
+
children,
|
|
19
|
+
closeOnSelect = true,
|
|
20
|
+
contentProps,
|
|
21
|
+
width,
|
|
22
|
+
height,
|
|
23
|
+
zIndex,
|
|
24
|
+
}: DropdownButtonProps) => {
|
|
25
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
26
|
+
|
|
27
|
+
const defaultButtonProps = {
|
|
28
|
+
onClick: () => undefined,
|
|
29
|
+
trackingId: 'dropdown-button',
|
|
30
|
+
...buttonProps,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<DropdownRoot isOpen={isOpen} onIsOpenChange={setIsOpen} closeOnSelect={closeOnSelect}>
|
|
35
|
+
<DropdownMenu.Trigger asChild>
|
|
36
|
+
<Button {...defaultButtonProps}>{buttonLabel}</Button>
|
|
37
|
+
</DropdownMenu.Trigger>
|
|
38
|
+
<DropdownMenu.Portal>
|
|
39
|
+
<StyledContent $width={width} $height={height} $zIndex={zIndex} {...contentProps}>
|
|
40
|
+
<ScrollableContent>{children}</ScrollableContent>
|
|
41
|
+
</StyledContent>
|
|
42
|
+
</DropdownMenu.Portal>
|
|
43
|
+
</DropdownRoot>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { DropdownContext } from './dropdown.context.js';
|
|
5
|
+
|
|
6
|
+
type DropdownRootProps = React.ComponentPropsWithRef<typeof DropdownMenu.Root> & {
|
|
7
|
+
closeOnSelect?: boolean;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
onIsOpenChange?: (isOpen: boolean) => void;
|
|
10
|
+
isOpen?: boolean;
|
|
11
|
+
contentProps?: React.ComponentPropsWithRef<typeof DropdownMenu.Content>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const DropdownRoot = ({
|
|
15
|
+
closeOnSelect = true,
|
|
16
|
+
children,
|
|
17
|
+
isOpen,
|
|
18
|
+
onIsOpenChange,
|
|
19
|
+
...props
|
|
20
|
+
}: DropdownRootProps) => {
|
|
21
|
+
const [internalIsOpen, setInternalIsOpen] = useState(false);
|
|
22
|
+
const isControlled = isOpen !== undefined && onIsOpenChange !== undefined;
|
|
23
|
+
const effectiveIsOpen = isControlled ? isOpen : internalIsOpen;
|
|
24
|
+
const effectiveSetIsOpen = isControlled ? onIsOpenChange : setInternalIsOpen;
|
|
25
|
+
const closeDropdown = useCallback(() => effectiveSetIsOpen(false), [effectiveSetIsOpen]);
|
|
26
|
+
const handleSelectAndClose = useCallback(
|
|
27
|
+
(event?: Event) => {
|
|
28
|
+
if (closeOnSelect) closeDropdown();
|
|
29
|
+
if (event) event.preventDefault();
|
|
30
|
+
},
|
|
31
|
+
[closeOnSelect, closeDropdown],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<DropdownContext.Provider value={{ handleSelectAndClose }}>
|
|
36
|
+
<DropdownMenu.Root open={effectiveIsOpen} onOpenChange={effectiveSetIsOpen} modal={false} {...props}>
|
|
37
|
+
{children}
|
|
38
|
+
</DropdownMenu.Root>
|
|
39
|
+
</DropdownContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
};
|