@moises.ai/design-system 3.10.12 → 3.10.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moises.ai/design-system",
3
- "version": "3.10.12",
3
+ "version": "3.10.14",
4
4
  "description": "Design System package based on @radix-ui/themes with custom defaults",
5
5
  "private": false,
6
6
  "type": "module",
@@ -16,7 +16,7 @@
16
16
 
17
17
  .upgradeItem:focus-visible {
18
18
  outline: 2px solid var(--neutral-alpha-8);
19
- outline-offset: -3px;
19
+ outline-offset: 2px;
20
20
  }
21
21
 
22
22
  .upgradeItem:hover {
@@ -318,9 +318,8 @@
318
318
  }
319
319
 
320
320
  .rowActionButton:focus-visible {
321
- outline: 2px solid var(--accent-8);
322
- outline-offset: -2px;
323
- border-radius: 6px;
321
+ outline: 2px solid var(--neutral-alpha-8);
322
+ outline-offset: 2px;
324
323
  }
325
324
 
326
325
  .DataTable :global(.rt-TableRow:hover) .projectCell:not(.dropdownColumn) :not(.artistText) {
@@ -33,7 +33,7 @@
33
33
  }
34
34
 
35
35
  &:focus-visible {
36
- outline: 2px solid var(--neutral-alpha-6);
36
+ outline: 2px solid var(--neutral-alpha-8);
37
37
  outline-offset: 2px;
38
38
  }
39
39
 
@@ -65,7 +65,7 @@
65
65
 
66
66
  .navItem:focus-visible {
67
67
  outline: 2px solid var(--neutral-alpha-8);
68
- outline-offset: -3px;
68
+ outline-offset: 2px;
69
69
  }
70
70
 
71
71
  .navItemSelected {
@@ -13,7 +13,7 @@
13
13
 
14
14
  .userProfile:focus-visible {
15
15
  outline: 2px solid var(--neutral-alpha-8);
16
- outline-offset: -3px;
16
+ outline-offset: 2px;
17
17
  }
18
18
 
19
19
  .userProfileInner {
@@ -16,7 +16,7 @@
16
16
 
17
17
  &:focus-visible {
18
18
  outline: 2px solid var(--neutral-alpha-8);
19
- outline-offset: -1px;
19
+ outline-offset: 2px;
20
20
  }
21
21
 
22
22
  &:hover,
@@ -289,7 +289,7 @@ sectionTitle {
289
289
 
290
290
  .setlistExpandButton:focus-visible {
291
291
  outline: 2px solid var(--neutral-alpha-8);
292
- outline-offset: -1px;
292
+ outline-offset: 2px;
293
293
  }
294
294
 
295
295
  .collapsedMask {
@@ -27,14 +27,19 @@ export const Shell = ({
27
27
  children,
28
28
  }) => {
29
29
  const [collapsed, setCollapsed] = useState(false)
30
+ const [isTemporarilyExpandedByDrag, setIsTemporarilyExpandedByDrag] =
31
+ useState(false)
30
32
  const { isMobile } = useMobileDrawer()
31
33
 
32
- const effectiveCollapsed = isMobile ? false : collapsed
34
+ const effectiveCollapsed =
35
+ isMobile ? false : collapsed && !isTemporarilyExpandedByDrag
33
36
 
34
37
  return (
35
38
  <>
36
39
  <Sidebar
37
40
  isCollapsed={collapsed}
41
+ isTemporarilyExpandedByDrag={isTemporarilyExpandedByDrag}
42
+ onTemporarilyExpandedByDragChange={setIsTemporarilyExpandedByDrag}
38
43
  onCollapsedChange={setCollapsed}
39
44
  tooltip={sidebarTooltip}
40
45
  >
@@ -2,7 +2,7 @@ import { Flex } from '@radix-ui/themes'
2
2
  import { IconButton } from '../IconButton/IconButton'
3
3
  import { Tooltip } from '../Tooltip/Tooltip'
4
4
  import styles from './Sidebar.module.css'
5
- import { useState } from 'react'
5
+ import { useCallback, useEffect, useState } from 'react'
6
6
  import classNames from 'classnames'
7
7
  import {
8
8
  MoisesIcon,
@@ -18,6 +18,8 @@ export const Sidebar = ({
18
18
  className,
19
19
  children,
20
20
  isCollapsed,
21
+ isTemporarilyExpandedByDrag,
22
+ onTemporarilyExpandedByDragChange,
21
23
  onCollapsedChange,
22
24
  onLogoClick,
23
25
  tooltip = 'Expand menu',
@@ -25,12 +27,29 @@ export const Sidebar = ({
25
27
  }) => {
26
28
  const { isMobile, isOpen: mobileOpen, open, close } = useMobileDrawer()
27
29
  const [isHovered, setIsHovered] = useState(false)
30
+ const [internalTemporarilyExpandedByDrag, setInternalTemporarilyExpandedByDrag] =
31
+ useState(false)
28
32
 
29
- const effectiveCollapsed = isMobile ? false : isCollapsed
33
+ const dragExpanded =
34
+ isTemporarilyExpandedByDrag ?? internalTemporarilyExpandedByDrag
35
+
36
+ const setDragExpanded = useCallback(
37
+ (value) => {
38
+ if (isTemporarilyExpandedByDrag == null) {
39
+ setInternalTemporarilyExpandedByDrag(value)
40
+ }
41
+ onTemporarilyExpandedByDragChange?.(value)
42
+ },
43
+ [isTemporarilyExpandedByDrag, onTemporarilyExpandedByDragChange],
44
+ )
45
+
46
+ const effectiveCollapsed =
47
+ isMobile ? false : isCollapsed && !dragExpanded
30
48
 
31
49
  const handleToggleCollapse = () => {
32
50
  if (isMobile) return
33
51
  setIsHovered(false)
52
+ setDragExpanded(false)
34
53
  onCollapsedChange((prev) => !prev)
35
54
  }
36
55
 
@@ -52,9 +71,57 @@ export const Sidebar = ({
52
71
  }
53
72
  }
54
73
 
74
+ const expandForDragHover = () => {
75
+ if (isMobile || !isCollapsed) return
76
+ setDragExpanded(true)
77
+ }
78
+
79
+ const collapseAfterDrag = () => {
80
+ setDragExpanded(false)
81
+ }
82
+
83
+ const handleSidebarPointerEnter = (e) => {
84
+ handleMouseEnter()
85
+ if (e.buttons > 0) {
86
+ expandForDragHover()
87
+ }
88
+ }
89
+
90
+ const handleSidebarDragEnter = () => {
91
+ expandForDragHover()
92
+ }
93
+
94
+ const handleSidebarDragOver = (e) => {
95
+ if (isMobile || !isCollapsed) return
96
+ e.preventDefault()
97
+ expandForDragHover()
98
+ }
99
+
55
100
  const desktopLogoAriaLabel = effectiveCollapsed ? 'Expand menu' : 'Moises'
56
101
  const mobileLogoAriaLabel = 'Moises'
57
102
 
103
+ useEffect(() => {
104
+ if (!dragExpanded) return undefined
105
+
106
+ const handlePointerUp = () => {
107
+ collapseAfterDrag()
108
+ }
109
+
110
+ const handleDragEnd = () => {
111
+ collapseAfterDrag()
112
+ }
113
+
114
+ window.addEventListener('pointerup', handlePointerUp)
115
+ window.addEventListener('dragend', handleDragEnd)
116
+ window.addEventListener('drop', handleDragEnd)
117
+
118
+ return () => {
119
+ window.removeEventListener('pointerup', handlePointerUp)
120
+ window.removeEventListener('dragend', handleDragEnd)
121
+ window.removeEventListener('drop', handleDragEnd)
122
+ }
123
+ }, [dragExpanded])
124
+
58
125
  const logoContent = (
59
126
  <>
60
127
  <div
@@ -156,8 +223,11 @@ export const Sidebar = ({
156
223
  <Flex
157
224
  direction="column"
158
225
  className={styles.desktopSidebar}
159
- onMouseEnter={handleMouseEnter}
226
+ onMouseEnter={handleSidebarPointerEnter}
160
227
  onMouseLeave={handleMouseLeave}
228
+ onDragEnter={handleSidebarDragEnter}
229
+ onDragOver={handleSidebarDragOver}
230
+ onDrop={collapseAfterDrag}
161
231
  >
162
232
  {effectiveCollapsed ? (
163
233
  <div className={styles.headerWrapper}>
@@ -292,9 +292,8 @@
292
292
 
293
293
  .logoButton:focus-visible,
294
294
  .toggleButton:focus-visible {
295
- outline: 2px solid var(--accent-8);
296
- outline-offset: 4px;
297
- border-radius: 6px;
295
+ outline: 2px solid var(--neutral-alpha-8);
296
+ outline-offset: 2px;
298
297
  }
299
298
 
300
299
  .headerCollapsed {
@@ -1,5 +1,9 @@
1
1
  import { useState } from 'react'
2
2
  import { Sidebar } from './Sidebar'
3
+ import { SidebarSection } from './SidebarSection/SidebarSection'
4
+ import { ProductsList } from '../ProductsList/ProductsList'
5
+ import { SetlistList } from '../SetlistList/SetlistList'
6
+ import { Flex, Text } from '../../index'
3
7
  import {
4
8
  MoisesLogoIcon,
5
9
  SparkBarsIcon,
@@ -246,3 +250,145 @@ export const Default = {
246
250
  )
247
251
  },
248
252
  }
253
+
254
+ export const DragHoverExpand = {
255
+ render: () => {
256
+ const [isCollapsed, setIsCollapsed] = useState(true)
257
+ const [isTemporarilyExpandedByDrag, setIsTemporarilyExpandedByDrag] =
258
+ useState(false)
259
+ const [selectedProduct, setSelectedProduct] = useState('library')
260
+ const [selectedSetlist, setSelectedSetlist] = useState('setlist-1')
261
+ const [isDraggingFile, setIsDraggingFile] = useState(false)
262
+ const effectiveCollapsed = isCollapsed && !isTemporarilyExpandedByDrag
263
+
264
+ const products = [
265
+ {
266
+ id: 'library',
267
+ label: 'Library',
268
+ icon: <LibraryIcon width={16} height={16} />,
269
+ },
270
+ {
271
+ id: 'ai-studio',
272
+ label: 'AI Studio',
273
+ icon: <SparkBarsIcon width={16} height={16} />,
274
+ },
275
+ {
276
+ id: 'mastering',
277
+ label: 'Mastering',
278
+ icon: <MasteringIcon width={16} height={16} />,
279
+ },
280
+ ]
281
+
282
+ const setlists = [
283
+ {
284
+ id: 'setlist-1',
285
+ label: 'Piano Exercises',
286
+ subtitle: 'By Berklee',
287
+ },
288
+ {
289
+ id: 'setlist-2',
290
+ label: 'Band Rehearsal',
291
+ subtitle: 'By Nickyz',
292
+ },
293
+ {
294
+ id: 'setlist-3',
295
+ label: 'Gig Dec 21th',
296
+ },
297
+ ]
298
+
299
+ return (
300
+ <>
301
+ <style>{`
302
+ .SidebarDragHoverStoryLayout {
303
+ display: flex;
304
+ min-height: 100vh;
305
+ min-height: 100dvh;
306
+ background: var(--neutral-2);
307
+ }
308
+
309
+ .SidebarDragHoverStoryMain {
310
+ flex: 1;
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ padding: 32px;
315
+ background: linear-gradient(180deg, var(--neutral-2) 0%, var(--neutral-1) 100%);
316
+ }
317
+
318
+ .SidebarDragHoverFile {
319
+ display: flex;
320
+ flex-direction: column;
321
+ gap: 8px;
322
+ width: 280px;
323
+ padding: 16px;
324
+ border-radius: 12px;
325
+ border: 1px solid var(--neutral-alpha-4);
326
+ background: var(--neutral-1);
327
+ box-shadow: 0 12px 24px -18px rgba(0, 0, 0, 0.45);
328
+ cursor: grab;
329
+ user-select: none;
330
+ }
331
+
332
+ .SidebarDragHoverFile[data-dragging='true'] {
333
+ opacity: 0.6;
334
+ cursor: grabbing;
335
+ }
336
+ `}</style>
337
+
338
+ <div className="SidebarDragHoverStoryLayout">
339
+ <Sidebar
340
+ isCollapsed={isCollapsed}
341
+ isTemporarilyExpandedByDrag={isTemporarilyExpandedByDrag}
342
+ onTemporarilyExpandedByDragChange={setIsTemporarilyExpandedByDrag}
343
+ onCollapsedChange={setIsCollapsed}
344
+ >
345
+ <Flex direction="column">
346
+ <SidebarSection title="PRODUCTS" collapsed={effectiveCollapsed} />
347
+ <ProductsList
348
+ products={products}
349
+ selectedProductId={selectedProduct}
350
+ onProductClick={setSelectedProduct}
351
+ collapsed={effectiveCollapsed}
352
+ />
353
+ </Flex>
354
+
355
+ <Flex
356
+ direction="column"
357
+ style={{ overflow: 'hidden', flex: 1, minHeight: 0 }}
358
+ >
359
+ <SidebarSection title="SETLISTS" collapsed={effectiveCollapsed} />
360
+ <SetlistList
361
+ setlists={setlists}
362
+ selectedSetlistId={selectedSetlist}
363
+ onSetlistClick={setSelectedSetlist}
364
+ onNewSetlistClick={() => console.log('New setlist clicked')}
365
+ collapsed={effectiveCollapsed}
366
+ />
367
+ </Flex>
368
+ </Sidebar>
369
+
370
+ <div className="SidebarDragHoverStoryMain">
371
+ <div
372
+ draggable
373
+ data-dragging={isDraggingFile}
374
+ className="SidebarDragHoverFile"
375
+ onDragStart={() => setIsDraggingFile(true)}
376
+ onDragEnd={() => {
377
+ setIsDraggingFile(false)
378
+ setIsTemporarilyExpandedByDrag(false)
379
+ }}
380
+ >
381
+ <Text size="2" weight="bold">
382
+ Drag this file to the sidebar
383
+ </Text>
384
+ <Text size="1" style={{ color: 'var(--neutral-alpha-11)' }}>
385
+ With the sidebar collapsed, drag this card over it. The sidebar should
386
+ expand on hover and collapse again when you release.
387
+ </Text>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ </>
392
+ )
393
+ },
394
+ }
@@ -1,4 +1,12 @@
1
- import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'
1
+ import React, {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react'
2
10
  import { Toast } from 'radix-ui'
3
11
  import { Callout } from '../Callout/Callout'
4
12
  import styles from './ToastProvider.module.css'
@@ -30,9 +38,31 @@ export const ToastProvider = ({
30
38
  return 'neutral'
31
39
  }, [])
32
40
 
41
+ const getCalloutStyle = useCallback((variant) => {
42
+ if (variant === 'success') {
43
+ return {
44
+ backgroundColor: '#113B29',
45
+ color: '#BBFFD7',
46
+ }
47
+ }
48
+ if (variant === 'error') {
49
+ return {
50
+ backgroundColor: '#201314',
51
+ color: '#FF9592',
52
+ }
53
+ }
54
+ return undefined
55
+ }, [])
56
+
33
57
  const [toasts, setToasts] = useState([])
58
+ const timeoutIdsRef = useRef(new Map())
34
59
 
35
60
  const dismiss = useCallback((id) => {
61
+ const timeoutId = timeoutIdsRef.current.get(id)
62
+ if (timeoutId) {
63
+ clearTimeout(timeoutId)
64
+ timeoutIdsRef.current.delete(id)
65
+ }
36
66
  setToasts((prev) => prev.filter((toastItem) => toastItem.id !== id))
37
67
  }, [])
38
68
 
@@ -43,10 +73,32 @@ export const ToastProvider = ({
43
73
  ...normalized,
44
74
  id: `${Date.now()}-${toastId++}`,
45
75
  }
46
- setToasts((prev) => [...prev.slice(-(maxToasts - 1)), nextToast])
76
+
77
+ const toastDuration = nextToast.duration ?? duration
78
+ const timeoutId = setTimeout(() => {
79
+ dismiss(nextToast.id)
80
+ }, toastDuration)
81
+
82
+ timeoutIdsRef.current.set(nextToast.id, timeoutId)
83
+ setToasts((prev) => {
84
+ const nextToasts = [...prev.slice(-(maxToasts - 1)), nextToast]
85
+ const nextToastIds = new Set(nextToasts.map((toastItem) => toastItem.id))
86
+
87
+ prev.forEach((toastItem) => {
88
+ if (!nextToastIds.has(toastItem.id)) {
89
+ const staleTimeoutId = timeoutIdsRef.current.get(toastItem.id)
90
+ if (staleTimeoutId) {
91
+ clearTimeout(staleTimeoutId)
92
+ timeoutIdsRef.current.delete(toastItem.id)
93
+ }
94
+ }
95
+ })
96
+
97
+ return nextToasts
98
+ })
47
99
  return nextToast.id
48
100
  },
49
- [maxToasts],
101
+ [dismiss, duration, maxToasts],
50
102
  )
51
103
 
52
104
  const success = useCallback(
@@ -73,6 +125,13 @@ export const ToastProvider = ({
73
125
  [show, success, error, dismiss],
74
126
  )
75
127
 
128
+ useEffect(() => {
129
+ return () => {
130
+ timeoutIdsRef.current.forEach((timeoutId) => clearTimeout(timeoutId))
131
+ timeoutIdsRef.current.clear()
132
+ }
133
+ }, [])
134
+
76
135
  return (
77
136
  <ToastContext.Provider value={value}>
78
137
  <Toast.Provider
@@ -98,6 +157,7 @@ export const ToastProvider = ({
98
157
  title={toastItem.title}
99
158
  text={toastItem.description}
100
159
  className={styles.toastCallout}
160
+ style={getCalloutStyle(toastItem.variant)}
101
161
  />
102
162
  </div>
103
163
  </Toast.Description>