@moises.ai/design-system 3.5.6 → 3.6.0
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/index.js +4261 -4030
- package/package.json +3 -2
- package/src/components/AdditionalItems/AdditionalItems.jsx +8 -0
- package/src/components/AdditionalItems/AdditionalItems.module.css +5 -0
- package/src/components/DropdownButton/DropdownButton.module.css +64 -0
- package/src/components/Extension/Extension.jsx +317 -0
- package/src/components/Extension/Extension.stories.jsx +368 -0
- package/src/components/ProductsList/ProductsList.jsx +8 -0
- package/src/components/ProductsList/ProductsList.module.css +5 -0
- package/src/components/ProfileMenu/MenuTrigger.jsx +2 -0
- package/src/components/ProfileMenu/ProfileMenu.module.css +5 -0
- package/src/components/Select/Select.jsx +5 -1
- package/src/components/Select/Select.module.css +67 -0
- package/src/components/SetlistList/SetlistList.jsx +57 -5
- package/src/components/SetlistList/SetlistList.module.css +34 -0
- package/src/components/Sidebar/Sidebar.module.css +1 -1
- package/src/components/Sidebar/SidebarSection/SidebarSection.module.css +1 -1
- package/src/components/useForm/useForm.jsx +204 -0
- package/src/components/useForm/useForm.stories.jsx +459 -0
- package/src/index.jsx +3 -0
- package/src/lib/menu/Menu.module.css +69 -4
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { Badge, Button, Card, Flex, IconButton, Text } from '../../index'
|
|
3
|
+
import { DotsVerticalIcon } from '../../icons'
|
|
4
|
+
import { Extension, useNavigation, useScreen } from './Extension'
|
|
5
|
+
|
|
6
|
+
const mockMoises = {
|
|
7
|
+
extension: { close: () => console.log('extension.close()') },
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const sidebarDecorator = (Story) => (
|
|
11
|
+
<div
|
|
12
|
+
style={{
|
|
13
|
+
width: 300,
|
|
14
|
+
height: 500,
|
|
15
|
+
border: '1px solid var(--gray-a6)',
|
|
16
|
+
borderRadius: 8,
|
|
17
|
+
overflow: 'hidden',
|
|
18
|
+
display: 'flex',
|
|
19
|
+
flexDirection: 'column',
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<Story />
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
title: 'Extensions/Extension',
|
|
28
|
+
decorators: [sidebarDecorator],
|
|
29
|
+
parameters: { layout: 'centered' },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Simple mode (flat layout, no navigation)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
export const Simple = {
|
|
37
|
+
name: 'Simple (Header + Content + Footer)',
|
|
38
|
+
render: () => (
|
|
39
|
+
<Extension moises={mockMoises}>
|
|
40
|
+
<Extension.Header title="My Extension">
|
|
41
|
+
<Button variant="ghost" size="1">Action</Button>
|
|
42
|
+
</Extension.Header>
|
|
43
|
+
<Extension.Content>
|
|
44
|
+
<Card>Card content 1</Card>
|
|
45
|
+
<Card>Card content 2</Card>
|
|
46
|
+
<Card>Card content 3</Card>
|
|
47
|
+
</Extension.Content>
|
|
48
|
+
<Extension.Footer>
|
|
49
|
+
<Button variant="ghost" size="1">Footer Action</Button>
|
|
50
|
+
</Extension.Footer>
|
|
51
|
+
</Extension>
|
|
52
|
+
),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const HeaderOnly = {
|
|
56
|
+
name: 'Header Only',
|
|
57
|
+
render: () => (
|
|
58
|
+
<Extension moises={mockMoises}>
|
|
59
|
+
<Extension.Header title="Minimal" />
|
|
60
|
+
<Extension.Content>
|
|
61
|
+
<Text size="2">Content with a simple header above.</Text>
|
|
62
|
+
</Extension.Content>
|
|
63
|
+
</Extension>
|
|
64
|
+
),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const HeaderWithBackButton = {
|
|
68
|
+
name: 'Header with Back Button',
|
|
69
|
+
render: () => (
|
|
70
|
+
<Extension moises={mockMoises}>
|
|
71
|
+
<Extension.Header title="Detail View" onBack={() => console.log('onBack')} />
|
|
72
|
+
<Extension.Content>
|
|
73
|
+
<Text size="2">Passing `onBack` renders a back arrow on the left.</Text>
|
|
74
|
+
</Extension.Content>
|
|
75
|
+
</Extension>
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const HeaderCustomLeft = {
|
|
80
|
+
name: 'Header with Custom Left',
|
|
81
|
+
render: () => (
|
|
82
|
+
<Extension moises={mockMoises}>
|
|
83
|
+
<Extension.Header
|
|
84
|
+
title="Custom Left"
|
|
85
|
+
headerLeft={<Badge variant="soft" size="1">v2</Badge>}
|
|
86
|
+
/>
|
|
87
|
+
<Extension.Content>
|
|
88
|
+
<Text size="2">`headerLeft` replaces the default back button.</Text>
|
|
89
|
+
</Extension.Content>
|
|
90
|
+
</Extension>
|
|
91
|
+
),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const HeaderCustomRight = {
|
|
95
|
+
name: 'Header with Custom Right',
|
|
96
|
+
render: () => (
|
|
97
|
+
<Extension moises={mockMoises}>
|
|
98
|
+
<Extension.Header
|
|
99
|
+
title="Options"
|
|
100
|
+
headerRight={
|
|
101
|
+
<IconButton variant="ghost" color="gray" size="1">
|
|
102
|
+
<DotsVerticalIcon width={16} />
|
|
103
|
+
</IconButton>
|
|
104
|
+
}
|
|
105
|
+
/>
|
|
106
|
+
<Extension.Content>
|
|
107
|
+
<Text size="2">`headerRight` adds actions before the close button.</Text>
|
|
108
|
+
</Extension.Content>
|
|
109
|
+
</Extension>
|
|
110
|
+
),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const HeaderNoClose = {
|
|
114
|
+
name: 'Header without Close Button',
|
|
115
|
+
render: () => (
|
|
116
|
+
<Extension moises={mockMoises}>
|
|
117
|
+
<Extension.Header title="No Close" allowClose={false} />
|
|
118
|
+
<Extension.Content>
|
|
119
|
+
<Text size="2">Close button hidden via `allowClose={'{false}'}`.</Text>
|
|
120
|
+
</Extension.Content>
|
|
121
|
+
</Extension>
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const ScrollableContent = {
|
|
126
|
+
name: 'Scrollable Content',
|
|
127
|
+
render: () => (
|
|
128
|
+
<Extension moises={mockMoises}>
|
|
129
|
+
<Extension.Header title="Scrollable" />
|
|
130
|
+
<Extension.Content>
|
|
131
|
+
{Array.from({ length: 20 }, (_, i) => (
|
|
132
|
+
<Card key={i}>Item {i + 1}</Card>
|
|
133
|
+
))}
|
|
134
|
+
</Extension.Content>
|
|
135
|
+
<Extension.Footer>
|
|
136
|
+
<Text size="1" color="gray">Footer stays pinned</Text>
|
|
137
|
+
</Extension.Footer>
|
|
138
|
+
</Extension>
|
|
139
|
+
),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Navigator mode (screen stack with push/pop)
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
const ITEMS = [
|
|
147
|
+
{ id: 'drums', name: 'Drums', desc: 'Acoustic drum kit with multiple mic positions' },
|
|
148
|
+
{ id: 'bass', name: 'Bass', desc: 'Electric bass recorded through DI and amp' },
|
|
149
|
+
{ id: 'guitar', name: 'Guitar', desc: 'Clean electric guitar with stereo chorus' },
|
|
150
|
+
{ id: 'vocals', name: 'Vocals', desc: 'Lead vocals with studio compression' },
|
|
151
|
+
{ id: 'keys', name: 'Keys', desc: 'Rhodes piano through a rotary speaker sim' },
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
function HomeScreen() {
|
|
155
|
+
const { push } = useNavigation()
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<>
|
|
159
|
+
<Extension.Content>
|
|
160
|
+
{ITEMS.map((item) => (
|
|
161
|
+
<Card
|
|
162
|
+
key={item.id}
|
|
163
|
+
style={{ cursor: 'pointer' }}
|
|
164
|
+
onClick={() => push('detail', { itemId: item.id, itemName: item.name })}
|
|
165
|
+
>
|
|
166
|
+
<Flex direction="column" gap="1">
|
|
167
|
+
<Text size="2" weight="medium">{item.name}</Text>
|
|
168
|
+
<Text size="1" color="gray">{item.desc}</Text>
|
|
169
|
+
</Flex>
|
|
170
|
+
</Card>
|
|
171
|
+
))}
|
|
172
|
+
</Extension.Content>
|
|
173
|
+
<Extension.Footer>
|
|
174
|
+
<Button variant="ghost" size="1" onClick={() => push('settings')}>
|
|
175
|
+
View Stack
|
|
176
|
+
</Button>
|
|
177
|
+
</Extension.Footer>
|
|
178
|
+
</>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function DetailScreen() {
|
|
183
|
+
const { push } = useNavigation()
|
|
184
|
+
const { params, setOptions } = useScreen()
|
|
185
|
+
const item = ITEMS.find((i) => i.id === params.itemId)
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
setOptions({ title: params.itemName })
|
|
189
|
+
}, [params.itemName])
|
|
190
|
+
|
|
191
|
+
if (!item) {
|
|
192
|
+
return (
|
|
193
|
+
<Extension.Content>
|
|
194
|
+
<Text size="2" color="gray">Item not found.</Text>
|
|
195
|
+
</Extension.Content>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<Extension.Content>
|
|
201
|
+
<Flex direction="column" gap="2">
|
|
202
|
+
<Text size="2" weight="medium">{item.name}</Text>
|
|
203
|
+
<Text size="2" color="gray">{item.desc}</Text>
|
|
204
|
+
</Flex>
|
|
205
|
+
<Button variant="soft" size="2" onClick={() => push('editor', { itemId: item.id })}>
|
|
206
|
+
Open Editor
|
|
207
|
+
</Button>
|
|
208
|
+
</Extension.Content>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function EditorScreen() {
|
|
213
|
+
const { setOptions } = useScreen()
|
|
214
|
+
const [dirty, setDirty] = useState(false)
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
setOptions({
|
|
218
|
+
headerRight: dirty ? (
|
|
219
|
+
<Button variant="soft" size="1" onClick={() => setDirty(false)}>Save</Button>
|
|
220
|
+
) : null,
|
|
221
|
+
})
|
|
222
|
+
}, [dirty])
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<Extension.Content>
|
|
226
|
+
<Text size="2" color="gray">
|
|
227
|
+
{dirty ? 'You have unsaved changes.' : 'No changes yet.'}
|
|
228
|
+
</Text>
|
|
229
|
+
{!dirty && (
|
|
230
|
+
<Button variant="soft" size="2" onClick={() => setDirty(true)}>
|
|
231
|
+
Make a change
|
|
232
|
+
</Button>
|
|
233
|
+
)}
|
|
234
|
+
</Extension.Content>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function SettingsScreen() {
|
|
239
|
+
const { stack } = useNavigation()
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<Extension.Content>
|
|
243
|
+
<Text size="2" weight="medium">Navigation Stack</Text>
|
|
244
|
+
<Flex direction="column" gap="1">
|
|
245
|
+
{stack.map((entry, i) => (
|
|
246
|
+
<Flex key={i} align="center" gap="2">
|
|
247
|
+
<Badge variant="soft" size="1">{i}</Badge>
|
|
248
|
+
<Text size="1">{entry.name}</Text>
|
|
249
|
+
</Flex>
|
|
250
|
+
))}
|
|
251
|
+
</Flex>
|
|
252
|
+
</Extension.Content>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export const NavigatorFull = {
|
|
257
|
+
name: 'Navigator (Multi-Screen)',
|
|
258
|
+
render: () => (
|
|
259
|
+
<Extension moises={mockMoises} initialScreen="home" animated>
|
|
260
|
+
<Extension.Screen name="home" title="Navigator Demo" component={HomeScreen}
|
|
261
|
+
headerRight={
|
|
262
|
+
<IconButton variant="ghost" color="gray" size="1">
|
|
263
|
+
<DotsVerticalIcon width={16} />
|
|
264
|
+
</IconButton>
|
|
265
|
+
}
|
|
266
|
+
/>
|
|
267
|
+
<Extension.Screen name="detail" title="Detail" component={DetailScreen} />
|
|
268
|
+
<Extension.Screen name="editor" title="Editor" component={EditorScreen} />
|
|
269
|
+
<Extension.Screen name="settings" title="Settings" component={SettingsScreen} />
|
|
270
|
+
</Extension>
|
|
271
|
+
),
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Navigator — minimal two-screen example
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
function ListScreen() {
|
|
279
|
+
const { push } = useNavigation()
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<Extension.Content>
|
|
283
|
+
<Text size="2" color="gray">A minimal two-screen navigator.</Text>
|
|
284
|
+
<Button variant="soft" size="2" onClick={() => push('about')}>
|
|
285
|
+
Go to About
|
|
286
|
+
</Button>
|
|
287
|
+
</Extension.Content>
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function AboutScreen() {
|
|
292
|
+
return (
|
|
293
|
+
<Extension.Content>
|
|
294
|
+
<Text size="2">This is the about screen. Press the back arrow to return.</Text>
|
|
295
|
+
</Extension.Content>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const NavigatorMinimal = {
|
|
300
|
+
name: 'Navigator (Minimal)',
|
|
301
|
+
render: () => (
|
|
302
|
+
<Extension moises={mockMoises} initialScreen="list" animated>
|
|
303
|
+
<Extension.Screen name="list" title="Home" component={ListScreen} />
|
|
304
|
+
<Extension.Screen name="about" title="About" component={AboutScreen} />
|
|
305
|
+
</Extension>
|
|
306
|
+
),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// Navigator — dynamic header via setOptions
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
function CounterScreen() {
|
|
314
|
+
const { setOptions } = useScreen()
|
|
315
|
+
const [count, setCount] = useState(0)
|
|
316
|
+
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
setOptions({
|
|
319
|
+
title: `Count: ${count}`,
|
|
320
|
+
headerRight: (
|
|
321
|
+
<Button variant="soft" size="1" onClick={() => setCount(0)}>Reset</Button>
|
|
322
|
+
),
|
|
323
|
+
})
|
|
324
|
+
}, [count])
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<Extension.Content>
|
|
328
|
+
<Button variant="soft" size="2" onClick={() => setCount((c) => c + 1)}>
|
|
329
|
+
Increment
|
|
330
|
+
</Button>
|
|
331
|
+
</Extension.Content>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export const DynamicHeader = {
|
|
336
|
+
name: 'Navigator (Dynamic Header)',
|
|
337
|
+
render: () => (
|
|
338
|
+
<Extension moises={mockMoises} initialScreen="counter">
|
|
339
|
+
<Extension.Screen name="counter" title="Count: 0" component={CounterScreen} />
|
|
340
|
+
</Extension>
|
|
341
|
+
),
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
// Navigator — custom close handler
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
function CloseInfoScreen() {
|
|
349
|
+
return (
|
|
350
|
+
<Extension.Content>
|
|
351
|
+
<Text size="2">The close button on this screen triggers a custom handler instead of `moises.extension.close()`.</Text>
|
|
352
|
+
</Extension.Content>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export const CustomClose = {
|
|
357
|
+
name: 'Navigator (Custom Close)',
|
|
358
|
+
render: () => (
|
|
359
|
+
<Extension moises={mockMoises} initialScreen="info">
|
|
360
|
+
<Extension.Screen
|
|
361
|
+
name="info"
|
|
362
|
+
title="Custom Close"
|
|
363
|
+
component={CloseInfoScreen}
|
|
364
|
+
onClose={() => console.log('custom onClose handler')}
|
|
365
|
+
/>
|
|
366
|
+
</Extension>
|
|
367
|
+
),
|
|
368
|
+
}
|
|
@@ -18,11 +18,19 @@ export const ProductsList = ({
|
|
|
18
18
|
align="center"
|
|
19
19
|
px="3"
|
|
20
20
|
py="2"
|
|
21
|
+
role="button"
|
|
22
|
+
tabIndex={0}
|
|
21
23
|
className={classNames(styles.navItem, {
|
|
22
24
|
[styles.navItemSelected]: isSelected,
|
|
23
25
|
[styles.collapsed]: collapsed,
|
|
24
26
|
})}
|
|
25
27
|
onClick={() => onProductClick?.(product.id, product)}
|
|
28
|
+
onKeyDown={(e) => {
|
|
29
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
30
|
+
e.preventDefault()
|
|
31
|
+
onProductClick?.(product.id, product)
|
|
32
|
+
}
|
|
33
|
+
}}
|
|
26
34
|
>
|
|
27
35
|
{product.icon && (
|
|
28
36
|
<div className={styles.navItemIcon}>{product.icon}</div>
|
|
@@ -16,6 +16,8 @@ export function MenuTrigger({ className, user, collapsed, ...props }) {
|
|
|
16
16
|
}
|
|
17
17
|
return (
|
|
18
18
|
<Flex
|
|
19
|
+
role="button"
|
|
20
|
+
tabIndex={0}
|
|
19
21
|
onMouseEnter={handleMouseEnter}
|
|
20
22
|
onMouseLeave={handleMouseLeave}
|
|
21
23
|
className={classNames(styles.userProfile, { [styles.collapsed]: collapsed }, className)}
|
|
@@ -69,7 +69,11 @@ export const Select = ({
|
|
|
69
69
|
</Text>
|
|
70
70
|
) : undefined}
|
|
71
71
|
</SelectRadix.Trigger>
|
|
72
|
-
<SelectRadix.Content
|
|
72
|
+
<SelectRadix.Content
|
|
73
|
+
position="popper"
|
|
74
|
+
className={styles.selectContent}
|
|
75
|
+
style={{ height: maxHeight }}
|
|
76
|
+
>
|
|
73
77
|
<SelectRadix.Group>
|
|
74
78
|
{items.map((item) =>
|
|
75
79
|
item.type === 'separator' ? (
|
|
@@ -1,3 +1,70 @@
|
|
|
1
|
+
/* Select content popover animation */
|
|
2
|
+
.selectContent {
|
|
3
|
+
animation-duration: 400ms;
|
|
4
|
+
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
|
5
|
+
will-change: transform, opacity;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.selectContent[data-side='top'] {
|
|
9
|
+
animation-name: slideDownAndFade;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.selectContent[data-side='right'] {
|
|
13
|
+
animation-name: slideLeftAndFade;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.selectContent[data-side='bottom'] {
|
|
17
|
+
animation-name: slideUpAndFade;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.selectContent[data-side='left'] {
|
|
21
|
+
animation-name: slideRightAndFade;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@keyframes slideUpAndFade {
|
|
25
|
+
from {
|
|
26
|
+
opacity: 0;
|
|
27
|
+
transform: translateY(2px);
|
|
28
|
+
}
|
|
29
|
+
to {
|
|
30
|
+
opacity: 1;
|
|
31
|
+
transform: translateY(0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@keyframes slideRightAndFade {
|
|
36
|
+
from {
|
|
37
|
+
opacity: 0;
|
|
38
|
+
transform: translateX(-2px);
|
|
39
|
+
}
|
|
40
|
+
to {
|
|
41
|
+
opacity: 1;
|
|
42
|
+
transform: translateX(0);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@keyframes slideDownAndFade {
|
|
47
|
+
from {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
transform: translateY(-2px);
|
|
50
|
+
}
|
|
51
|
+
to {
|
|
52
|
+
opacity: 1;
|
|
53
|
+
transform: translateY(0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes slideLeftAndFade {
|
|
58
|
+
from {
|
|
59
|
+
opacity: 0;
|
|
60
|
+
transform: translateX(2px);
|
|
61
|
+
}
|
|
62
|
+
to {
|
|
63
|
+
opacity: 1;
|
|
64
|
+
transform: translateX(0);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
1
68
|
.selectTrigger.soft:focus-visible,
|
|
2
69
|
.selectTrigger.ghost:focus-visible {
|
|
3
70
|
outline: none;
|
|
@@ -17,6 +17,7 @@ export const SetlistItem = ({
|
|
|
17
17
|
onClick,
|
|
18
18
|
className,
|
|
19
19
|
style,
|
|
20
|
+
avatarStyle,
|
|
20
21
|
avatar,
|
|
21
22
|
text,
|
|
22
23
|
subtitle,
|
|
@@ -50,6 +51,8 @@ export const SetlistItem = ({
|
|
|
50
51
|
<Flex
|
|
51
52
|
gap="3"
|
|
52
53
|
align="center"
|
|
54
|
+
role="button"
|
|
55
|
+
tabIndex={0}
|
|
53
56
|
onClick={onClick}
|
|
54
57
|
className={classNames(styles.newSetlistItem, className, {
|
|
55
58
|
[styles.navItemSelected]: isSelected,
|
|
@@ -58,6 +61,12 @@ export const SetlistItem = ({
|
|
|
58
61
|
})}
|
|
59
62
|
onMouseEnter={handleMouseEnter}
|
|
60
63
|
onMouseLeave={handleMouseLeave}
|
|
64
|
+
onKeyDown={(e) => {
|
|
65
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
onClick?.()
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
61
70
|
style={style}
|
|
62
71
|
>
|
|
63
72
|
<Flex
|
|
@@ -68,13 +77,14 @@ export const SetlistItem = ({
|
|
|
68
77
|
})}
|
|
69
78
|
>
|
|
70
79
|
{avatar && (
|
|
71
|
-
<div className={styles.avatarSetlist}>
|
|
80
|
+
<div className={styles.avatarSetlist} style={avatarStyle}>
|
|
72
81
|
{typeof avatar === 'string' ? (
|
|
73
82
|
<Avatar
|
|
74
83
|
src={avatar}
|
|
75
84
|
fallback={<SetlistIcon width={16} height={16} />}
|
|
76
|
-
size="3"
|
|
85
|
+
// size="3"
|
|
77
86
|
radius="small"
|
|
87
|
+
style={avatarStyle}
|
|
78
88
|
/>
|
|
79
89
|
) : (
|
|
80
90
|
avatar
|
|
@@ -141,9 +151,11 @@ export const SetlistList = ({
|
|
|
141
151
|
}) => {
|
|
142
152
|
const [openSetlistMenuId, setOpenSetlistMenuId] = useState(null)
|
|
143
153
|
const [isHoveredWhenCollapsed, setIsHoveredWhenCollapsed] = useState(false)
|
|
154
|
+
const [isFullyCollapsed, setIsFullyCollapsed] = useState(false)
|
|
144
155
|
const hoverTimeoutRef = useRef(null)
|
|
156
|
+
const collapseCenterTimeoutRef = useRef(null)
|
|
145
157
|
const collapsedItemHeight = 44
|
|
146
|
-
const collapsedVisibleOffset =
|
|
158
|
+
const collapsedVisibleOffset = 8
|
|
147
159
|
const maxCollapsedItems = 4
|
|
148
160
|
const collapsedNewItemOffset = collapsed && onNewSetlistClick ? 1 : 0
|
|
149
161
|
const showCollapsedStack = collapsed && !isHoveredWhenCollapsed
|
|
@@ -170,6 +182,26 @@ export const SetlistList = ({
|
|
|
170
182
|
}, 180)
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (showCollapsedStack) {
|
|
187
|
+
collapseCenterTimeoutRef.current = setTimeout(() => {
|
|
188
|
+
setIsFullyCollapsed(true)
|
|
189
|
+
collapseCenterTimeoutRef.current = null
|
|
190
|
+
}, 200)
|
|
191
|
+
} else {
|
|
192
|
+
setIsFullyCollapsed(false)
|
|
193
|
+
if (collapseCenterTimeoutRef.current) {
|
|
194
|
+
clearTimeout(collapseCenterTimeoutRef.current)
|
|
195
|
+
collapseCenterTimeoutRef.current = null
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return () => {
|
|
199
|
+
if (collapseCenterTimeoutRef.current) {
|
|
200
|
+
clearTimeout(collapseCenterTimeoutRef.current)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}, [showCollapsedStack])
|
|
204
|
+
|
|
173
205
|
useEffect(() => {
|
|
174
206
|
return () => {
|
|
175
207
|
if (hoverTimeoutRef.current) {
|
|
@@ -200,7 +232,9 @@ export const SetlistList = ({
|
|
|
200
232
|
</div>
|
|
201
233
|
)
|
|
202
234
|
|
|
203
|
-
const
|
|
235
|
+
const collapsedStackAvatarSizes = ['38px', '37px', '36px', '35px']
|
|
236
|
+
|
|
237
|
+
const getCollapsedStackStyle = (index, setlist) => {
|
|
204
238
|
if (!collapsed) return undefined
|
|
205
239
|
if (!showCollapsedStack) {
|
|
206
240
|
return {
|
|
@@ -211,6 +245,14 @@ export const SetlistList = ({
|
|
|
211
245
|
}
|
|
212
246
|
const stackPosition = index + collapsedNewItemOffset
|
|
213
247
|
const zIndex = setlists.length + collapsedNewItemOffset - stackPosition
|
|
248
|
+
|
|
249
|
+
const boxShadows = [
|
|
250
|
+
'0 3.84px 15.36px -7.68px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 2.88px 11.52px -3.84px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 1.92px 2.88px -1.92px var(--Overlays-Black-Alpha-3, rgba(0, 0, 0, 0.15))',
|
|
251
|
+
'0 3.686px 14.746px -7.373px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 2.765px 11.059px -3.686px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 1.843px 2.765px -1.843px var(--Overlays-Black-Alpha-3, rgba(0, 0, 0, 0.15))',
|
|
252
|
+
'0 3.539px 14.156px -7.078px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 2.654px 10.617px -3.539px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 1.769px 2.654px -1.769px var(--Overlays-Black-Alpha-3, rgba(0, 0, 0, 0.15))',
|
|
253
|
+
'0 3.401px 13.603px -6.801px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 2.551px 10.202px -3.401px var(--Overlays-Black-Alpha-2, rgba(0, 0, 0, 0.10)), 0 1.701px 2.551px -1.701px var(--Overlays-Black-Alpha-3, rgba(0, 0, 0, 0.15))',
|
|
254
|
+
]
|
|
255
|
+
|
|
214
256
|
return {
|
|
215
257
|
position: 'relative',
|
|
216
258
|
marginTop:
|
|
@@ -218,6 +260,7 @@ export const SetlistList = ({
|
|
|
218
260
|
? 0
|
|
219
261
|
: -(collapsedItemHeight - collapsedVisibleOffset),
|
|
220
262
|
zIndex,
|
|
263
|
+
boxShadow: boxShadows[index],
|
|
221
264
|
}
|
|
222
265
|
}
|
|
223
266
|
|
|
@@ -239,6 +282,7 @@ export const SetlistList = ({
|
|
|
239
282
|
direction="column"
|
|
240
283
|
className={classNames(styles.setlistsContent, {
|
|
241
284
|
[styles.collapsedStack]: showCollapsedStack,
|
|
285
|
+
[styles.collapsedStackCentered]: isFullyCollapsed,
|
|
242
286
|
[styles.collapsedMask]: collapsed,
|
|
243
287
|
})}
|
|
244
288
|
>
|
|
@@ -283,6 +327,14 @@ export const SetlistList = ({
|
|
|
283
327
|
setOpenSetlistMenuId(nextOpen ? setlist.id : null)
|
|
284
328
|
}}
|
|
285
329
|
avatar={setlist.icon || <SetlistIcon width={16} height={16} />}
|
|
330
|
+
avatarStyle={
|
|
331
|
+
showCollapsedStack
|
|
332
|
+
? {
|
|
333
|
+
width: collapsedStackAvatarSizes[index],
|
|
334
|
+
height: collapsedStackAvatarSizes[index],
|
|
335
|
+
}
|
|
336
|
+
: undefined
|
|
337
|
+
}
|
|
286
338
|
group={setlist.group}
|
|
287
339
|
moises={setlist.moises}
|
|
288
340
|
text={setlist.label}
|
|
@@ -294,7 +346,7 @@ export const SetlistList = ({
|
|
|
294
346
|
[styles.collapsedStackItem]: showCollapsedStack,
|
|
295
347
|
[styles.collapsedTransition]: collapsed,
|
|
296
348
|
})}
|
|
297
|
-
style={getCollapsedStackStyle(index)}
|
|
349
|
+
style={getCollapsedStackStyle(index, setlist)}
|
|
298
350
|
/>
|
|
299
351
|
)
|
|
300
352
|
})}
|