@moises.ai/design-system 4.19.6 → 4.19.8
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/dist/{AutomationPenIcon-BPBwkoCg.js → AutomationPenIcon-Dd1JocrI.js} +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.js +2853 -2758
- package/package.json +1 -1
- package/src/components/ContextMenu/ContextMenu.stories.jsx +90 -0
- package/src/components/DropdownMenu/DropdownMenu.stories.jsx +87 -0
- package/src/components/Select/Select.jsx +113 -36
- package/src/components/Select/Select.module.css +55 -5
- package/src/components/Select/Select.stories.jsx +70 -0
- package/src/components/TextField/TextField.jsx +58 -32
- package/src/components/TextField/TextField.module.css +21 -0
- package/src/components/TextField/TextField.stories.jsx +35 -0
- package/src/icons/ArrowRightIcon.jsx +1 -1
- package/src/lib/menu/Menu.module.css +15 -3
- package/src/lib/menu/renderItem.jsx +20 -7
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Flex } from '@radix-ui/themes'
|
|
2
2
|
import { Select } from './Select'
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
+
import { LockIcon, StarIcon } from '../../icons'
|
|
5
|
+
import { GearIcon } from '@radix-ui/react-icons'
|
|
4
6
|
|
|
5
7
|
export default {
|
|
6
8
|
title: 'Components/Select',
|
|
@@ -41,6 +43,8 @@ import { Select } from '@moises.ai/design-system'
|
|
|
41
43
|
items: {
|
|
42
44
|
value: string, // Value of the option (required)
|
|
43
45
|
label: string, // Label of the option (required)
|
|
46
|
+
disabled?: boolean, // Disables this option (still visible in the menu)
|
|
47
|
+
rightIcon?: ReactNode, // Optional icon displayed on the right side
|
|
44
48
|
aiDetected?: boolean, // Shows ✦ icon before label if true (optional)
|
|
45
49
|
extra?: string | ReactNode, // Extra text or element after label (optional)
|
|
46
50
|
}[], // List of items to display in the select
|
|
@@ -52,11 +56,13 @@ import { Select } from '@moises.ai/design-system'
|
|
|
52
56
|
size?: '1' | '2' | '3', // Size variant of the select (default: '2')
|
|
53
57
|
variant?: 'soft' | 'ghost', // Visual style variant (default: 'soft')
|
|
54
58
|
disabled?: boolean, // Whether the select is disabled (default: false)
|
|
59
|
+
allowClickWhenDisabled?: boolean, // Keeps disabled look, opens menu with disabled options, fires onFocus on open (e.g. paywall)
|
|
55
60
|
className?: string, // Additional CSS class name
|
|
56
61
|
readOnly?: boolean, // Whether the select is read-only (default: false)
|
|
57
62
|
maxHeight?: string, // Max height of the dropdown (e.g. '200px')
|
|
58
63
|
showSelectIndicator?: boolean, // Show the default Radix select indicator (default: false)
|
|
59
64
|
maxWidth?: string, // Max width of the select (e.g. '300px')
|
|
65
|
+
onFocus?: () => void, // Called when opening while disabled + allowClickWhenDisabled
|
|
60
66
|
}
|
|
61
67
|
\`\`\`
|
|
62
68
|
`,
|
|
@@ -81,6 +87,12 @@ import { Select } from '@moises.ai/design-system'
|
|
|
81
87
|
description: 'Whether the select is disabled',
|
|
82
88
|
defaultValue: false,
|
|
83
89
|
},
|
|
90
|
+
allowClickWhenDisabled: {
|
|
91
|
+
control: 'boolean',
|
|
92
|
+
description:
|
|
93
|
+
'When true with disabled, keeps disabled look, opens menu with disabled options, and fires onFocus on open',
|
|
94
|
+
defaultValue: false,
|
|
95
|
+
},
|
|
84
96
|
showSelectIndicator: {
|
|
85
97
|
control: 'boolean',
|
|
86
98
|
description: 'Whether to show the default Radix select indicator',
|
|
@@ -191,6 +203,64 @@ export const Disabled = {
|
|
|
191
203
|
decorators: [withFlexContainer],
|
|
192
204
|
}
|
|
193
205
|
|
|
206
|
+
export const AllowClickWhenDisabled = {
|
|
207
|
+
render: () => {
|
|
208
|
+
const [lastAction, setLastAction] = useState('none yet')
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<Flex direction="column" gap="2" width="300px">
|
|
212
|
+
<Select
|
|
213
|
+
title="Instrument"
|
|
214
|
+
info="Looks disabled, opens menu and fires onFocus"
|
|
215
|
+
defaultValue="bass"
|
|
216
|
+
size="2"
|
|
217
|
+
disabled
|
|
218
|
+
allowClickWhenDisabled
|
|
219
|
+
items={[
|
|
220
|
+
{ value: 'bass', label: 'Bass' },
|
|
221
|
+
{ value: 'guitar', label: 'Guitar' },
|
|
222
|
+
{ value: 'piano', label: 'Piano' },
|
|
223
|
+
]}
|
|
224
|
+
onFocus={() => {
|
|
225
|
+
setLastAction('activated (focus)')
|
|
226
|
+
alert('paywall opened')
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
<div style={{ fontSize: 14, color: '#4b5563' }}>
|
|
230
|
+
Last action: {lastAction}
|
|
231
|
+
</div>
|
|
232
|
+
</Flex>
|
|
233
|
+
)
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const WithRightIcon = {
|
|
238
|
+
args: {
|
|
239
|
+
title: 'Instrument',
|
|
240
|
+
defaultValue: 'bass',
|
|
241
|
+
size: '2',
|
|
242
|
+
items: [
|
|
243
|
+
{
|
|
244
|
+
value: 'bass',
|
|
245
|
+
label: 'Bass',
|
|
246
|
+
rightIcon: <LockIcon width={14} height={14} />,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
value: 'guitar',
|
|
250
|
+
label: 'Guitar',
|
|
251
|
+
rightIcon: <StarIcon width={14} height={14} />,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
value: 'piano',
|
|
255
|
+
label: 'Piano',
|
|
256
|
+
rightIcon: <GearIcon width={14} height={14} />,
|
|
257
|
+
},
|
|
258
|
+
{ value: 'drums', label: 'Drums' },
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
decorators: [withFlexContainer],
|
|
262
|
+
}
|
|
263
|
+
|
|
194
264
|
export const DisabledGhost = {
|
|
195
265
|
args: {
|
|
196
266
|
title: 'Instrument',
|
|
@@ -12,6 +12,7 @@ export const TextField = ({
|
|
|
12
12
|
className,
|
|
13
13
|
disabled,
|
|
14
14
|
readOnly,
|
|
15
|
+
allowClickWhenDisabled = false,
|
|
15
16
|
size = '2',
|
|
16
17
|
Icon,
|
|
17
18
|
focusRing = false,
|
|
@@ -23,6 +24,7 @@ export const TextField = ({
|
|
|
23
24
|
aiDetected = false,
|
|
24
25
|
textAlign = 'left',
|
|
25
26
|
style,
|
|
27
|
+
onFocus,
|
|
26
28
|
...props
|
|
27
29
|
}) => {
|
|
28
30
|
// legacy Icon prop is kept for backwards compatibility.
|
|
@@ -31,6 +33,8 @@ export const TextField = ({
|
|
|
31
33
|
(Icon ? <Icon width={16} height={16} className={styles.icon} /> : null)
|
|
32
34
|
|
|
33
35
|
const chars = String(value ?? '').length
|
|
36
|
+
const isDisabled = Boolean(disabled)
|
|
37
|
+
const isDisabledClickable = isDisabled && Boolean(allowClickWhenDisabled)
|
|
34
38
|
|
|
35
39
|
return (
|
|
36
40
|
<Flex direction="column" width="100%" height="100%" gap="1">
|
|
@@ -42,39 +46,61 @@ export const TextField = ({
|
|
|
42
46
|
{info && <TooltipWithInfoIcon content={info} />}
|
|
43
47
|
</Flex>
|
|
44
48
|
)}
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
49
|
+
<div className={styles.fieldWrapper}>
|
|
50
|
+
<TextFieldRadix.Root
|
|
51
|
+
size={size}
|
|
52
|
+
value={value}
|
|
53
|
+
onChange={isDisabledClickable ? undefined : onChange}
|
|
54
|
+
placeholder={placeholder}
|
|
55
|
+
className={classNames(styles.TextField, className, {
|
|
56
|
+
[styles.Disabled]: isDisabled,
|
|
57
|
+
[styles.ReadOnly]: readOnly && !isDisabled,
|
|
58
|
+
[styles.focusRing]: focusRing || error,
|
|
59
|
+
[styles.errorText]: error,
|
|
60
|
+
[styles[`size${size}`]]: size,
|
|
61
|
+
[styles.alignCenter]: textAlign === 'center',
|
|
62
|
+
[styles.alignLeft]: textAlign === 'left',
|
|
63
|
+
})}
|
|
64
|
+
style={{
|
|
65
|
+
...(aiDetected && { '--ai-chars': chars }),
|
|
66
|
+
...style,
|
|
67
|
+
}}
|
|
68
|
+
autoFocus={autoFocus}
|
|
69
|
+
{...props}
|
|
70
|
+
onFocus={isDisabledClickable ? undefined : onFocus}
|
|
71
|
+
readOnly={readOnly}
|
|
72
|
+
disabled={isDisabled}
|
|
73
|
+
>
|
|
74
|
+
{resolvedStartSlot && (
|
|
75
|
+
<TextFieldRadix.Slot>{resolvedStartSlot}</TextFieldRadix.Slot>
|
|
76
|
+
)}
|
|
77
|
+
{aiDetected && (
|
|
78
|
+
<span className={styles.aiDetected} aria-hidden>
|
|
79
|
+
<span className={styles.aiDetectedMark}>✦</span>
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
82
|
+
{endSlot && <div className={styles.endSlot}>{endSlot}</div>}
|
|
83
|
+
</TextFieldRadix.Root>
|
|
84
|
+
{isDisabledClickable && (
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
className={styles.disabledClickOverlay}
|
|
88
|
+
aria-label={title || placeholder || 'Activate'}
|
|
89
|
+
tabIndex={0}
|
|
90
|
+
onPointerDown={(event) => {
|
|
91
|
+
// Prevent focus so dialogs/alerts don't restore focus and re-trigger.
|
|
92
|
+
event.preventDefault()
|
|
93
|
+
onFocus?.(event)
|
|
94
|
+
}}
|
|
95
|
+
onKeyDown={(event) => {
|
|
96
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
97
|
+
event.preventDefault()
|
|
98
|
+
onFocus?.(event)
|
|
99
|
+
}
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
75
102
|
)}
|
|
76
|
-
|
|
77
|
-
</TextFieldRadix.Root>
|
|
103
|
+
</div>
|
|
78
104
|
{secondaryText && (
|
|
79
105
|
<Text
|
|
80
106
|
size="1"
|
|
@@ -77,6 +77,27 @@
|
|
|
77
77
|
cursor: default;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
.fieldWrapper {
|
|
81
|
+
position: relative;
|
|
82
|
+
width: 100%;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.disabledClickOverlay {
|
|
86
|
+
position: absolute;
|
|
87
|
+
inset: 0;
|
|
88
|
+
z-index: 1;
|
|
89
|
+
margin: 0;
|
|
90
|
+
padding: 0;
|
|
91
|
+
border: none;
|
|
92
|
+
border-radius: inherit;
|
|
93
|
+
background: transparent;
|
|
94
|
+
cursor: default;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.disabledClickOverlay:focus {
|
|
98
|
+
outline: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
80
101
|
.ReadOnly {
|
|
81
102
|
background-color: var(--neutral-alpha-2) !important;
|
|
82
103
|
color: var(--neutral-12);
|
|
@@ -66,6 +66,15 @@ const MyComponent = () => (
|
|
|
66
66
|
defaultValue: { summary: 'false' },
|
|
67
67
|
},
|
|
68
68
|
},
|
|
69
|
+
allowClickWhenDisabled: {
|
|
70
|
+
control: 'boolean',
|
|
71
|
+
description:
|
|
72
|
+
'When true with disabled, keeps the disabled look and blocks typing, but still allows focus (e.g. paywall via onFocus)',
|
|
73
|
+
table: {
|
|
74
|
+
type: { summary: 'boolean' },
|
|
75
|
+
defaultValue: { summary: 'false' },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
69
78
|
readOnly: {
|
|
70
79
|
control: 'boolean',
|
|
71
80
|
description: 'Whether the input is read-only',
|
|
@@ -238,6 +247,32 @@ export const Disabled = {
|
|
|
238
247
|
},
|
|
239
248
|
}
|
|
240
249
|
|
|
250
|
+
export const AllowClickWhenDisabled = {
|
|
251
|
+
render: (args) => {
|
|
252
|
+
const [lastAction, setLastAction] = React.useState('none yet')
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
256
|
+
<TextField
|
|
257
|
+
{...args}
|
|
258
|
+
disabled
|
|
259
|
+
allowClickWhenDisabled
|
|
260
|
+
value="Looks disabled, click to activate"
|
|
261
|
+
onFocus={() => {
|
|
262
|
+
setLastAction('activated (focus)')
|
|
263
|
+
action('activated (focus)')()
|
|
264
|
+
alert('paywall opened')
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
<div style={{ fontSize: 14, color: '#4b5563' }}>Last action: {lastAction}</div>
|
|
268
|
+
</div>
|
|
269
|
+
)
|
|
270
|
+
},
|
|
271
|
+
args: {
|
|
272
|
+
...Default.args,
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
|
|
241
276
|
export const ReadOnly = {
|
|
242
277
|
args: {
|
|
243
278
|
...Default.args,
|
|
@@ -5,7 +5,7 @@ export const ArrowRightIcon = ({
|
|
|
5
5
|
...props
|
|
6
6
|
}) => (
|
|
7
7
|
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none" {...props} aria-label="Arrow Right Icon">
|
|
8
|
-
<path d="M13.1429 19L20 12L13.1429 5M18.8571 12L4 12" stroke="
|
|
8
|
+
<path d="M13.1429 19L20 12L13.1429 5M18.8571 12L4 12" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
|
9
9
|
</svg>
|
|
10
10
|
)
|
|
11
11
|
|
|
@@ -137,10 +137,9 @@
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
.menuItem[data-disabled]
|
|
140
|
+
.menuItem[data-disabled],
|
|
141
|
+
.menuItemDisabledClickable {
|
|
141
142
|
color: var(--neutral-alpha-7);
|
|
142
|
-
cursor: not-allowed;
|
|
143
|
-
pointer-events: none;
|
|
144
143
|
|
|
145
144
|
& .label,
|
|
146
145
|
& .secondaryTextBottom,
|
|
@@ -159,6 +158,15 @@
|
|
|
159
158
|
}
|
|
160
159
|
}
|
|
161
160
|
|
|
161
|
+
.menuItem[data-disabled] {
|
|
162
|
+
cursor: not-allowed;
|
|
163
|
+
pointer-events: none;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.menuItemDisabledClickable {
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
}
|
|
169
|
+
|
|
162
170
|
.menuSeparator {
|
|
163
171
|
height: 1px;
|
|
164
172
|
background-color: var(--neutral-alpha-3);
|
|
@@ -298,6 +306,10 @@
|
|
|
298
306
|
background-color: var(--neutral-alpha-3);
|
|
299
307
|
}
|
|
300
308
|
|
|
309
|
+
.menuItem.menuItemDisabledClickable[data-highlighted] {
|
|
310
|
+
background-color: transparent;
|
|
311
|
+
}
|
|
312
|
+
|
|
301
313
|
.menuSubContent {
|
|
302
314
|
min-width: 242px;
|
|
303
315
|
}
|
|
@@ -18,6 +18,11 @@ export const createRenderItem = (MenuPrimitives) => {
|
|
|
18
18
|
closeMenu,
|
|
19
19
|
showSelectedItemBackground = true,
|
|
20
20
|
) => {
|
|
21
|
+
const isDisabled = Boolean(opt.disabled)
|
|
22
|
+
const allowClickWhenDisabled = Boolean(opt.allowClickWhenDisabled)
|
|
23
|
+
const isRadixDisabled = isDisabled && !allowClickWhenDisabled
|
|
24
|
+
const isDisabledClickable = isDisabled && allowClickWhenDisabled
|
|
25
|
+
|
|
21
26
|
switch (opt.type) {
|
|
22
27
|
case 'custom':
|
|
23
28
|
return (
|
|
@@ -27,13 +32,17 @@ export const createRenderItem = (MenuPrimitives) => {
|
|
|
27
32
|
styles.menuItem,
|
|
28
33
|
styles.menuCustomRender,
|
|
29
34
|
styles[`menuItem-size-${size}`],
|
|
35
|
+
{
|
|
36
|
+
[styles.menuItemDisabledClickable]: isDisabledClickable,
|
|
37
|
+
},
|
|
30
38
|
)}
|
|
31
39
|
onSelect={(event) => {
|
|
32
40
|
if (!closeOnSelect) {
|
|
33
41
|
event.preventDefault()
|
|
34
42
|
}
|
|
35
43
|
}}
|
|
36
|
-
disabled={
|
|
44
|
+
disabled={isRadixDisabled}
|
|
45
|
+
aria-disabled={isDisabledClickable || undefined}
|
|
37
46
|
asChild
|
|
38
47
|
>
|
|
39
48
|
{opt.customRender(closeMenu)}
|
|
@@ -104,8 +113,9 @@ export const createRenderItem = (MenuPrimitives) => {
|
|
|
104
113
|
styles.menuItem,
|
|
105
114
|
styles[`menuItem-size-${size}`],
|
|
106
115
|
{
|
|
107
|
-
[styles.red]: opt.color === 'red' && !
|
|
108
|
-
[styles.cyan]: opt.color === 'cyan' && !
|
|
116
|
+
[styles.red]: opt.color === 'red' && !isDisabled,
|
|
117
|
+
[styles.cyan]: opt.color === 'cyan' && !isDisabled,
|
|
118
|
+
[styles.menuItemDisabledClickable]: isDisabledClickable,
|
|
109
119
|
},
|
|
110
120
|
)}
|
|
111
121
|
onSelect={(event) => {
|
|
@@ -115,7 +125,8 @@ export const createRenderItem = (MenuPrimitives) => {
|
|
|
115
125
|
opt.onClick?.()
|
|
116
126
|
onSelectionChange?.(opt)
|
|
117
127
|
}}
|
|
118
|
-
disabled={
|
|
128
|
+
disabled={isRadixDisabled}
|
|
129
|
+
aria-disabled={isDisabledClickable || undefined}
|
|
119
130
|
>
|
|
120
131
|
{opt.icon && <span className={styles.leftIcon}>{opt.icon}</span>}
|
|
121
132
|
<div className={styles.labelContainer}>
|
|
@@ -163,11 +174,13 @@ export const createRenderItem = (MenuPrimitives) => {
|
|
|
163
174
|
[styles.menuItemSelected]:
|
|
164
175
|
opt.checked && showSelectedItemBackground,
|
|
165
176
|
[styles[`menuItem-size-${size}`]]: size,
|
|
166
|
-
[styles.red]: opt.color === 'red' && !
|
|
167
|
-
[styles.cyan]: opt.color === 'cyan' && !
|
|
177
|
+
[styles.red]: opt.color === 'red' && !isDisabled,
|
|
178
|
+
[styles.cyan]: opt.color === 'cyan' && !isDisabled,
|
|
179
|
+
[styles.menuItemDisabledClickable]: isDisabledClickable,
|
|
168
180
|
},
|
|
169
181
|
)}
|
|
170
|
-
disabled={
|
|
182
|
+
disabled={isRadixDisabled}
|
|
183
|
+
aria-disabled={isDisabledClickable || undefined}
|
|
171
184
|
>
|
|
172
185
|
<span className={styles.checkboxIndicatorSlot}>
|
|
173
186
|
<MenuPrimitives.ItemIndicator
|