@moises.ai/design-system 3.10.6 → 3.10.7

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.
@@ -1,5 +1,17 @@
1
- import { useState } from 'react'
2
- import { SetlistList } from './SetlistList'
1
+ import { useState, useMemo, useCallback } from 'react'
2
+ import {
3
+ DndContext,
4
+ DragOverlay,
5
+ PointerSensor,
6
+ useSensor,
7
+ useSensors,
8
+ pointerWithin,
9
+ } from '@dnd-kit/core'
10
+ import { snapCenterToCursor } from '@dnd-kit/modifiers'
11
+ import { SetlistList, SetlistItem } from './SetlistList'
12
+ import { DataTable } from '../DataTable/DataTable'
13
+ import { DataTableDragOverlay } from '../DataTable/dnd/DataTableDragOverlay'
14
+ import { toDragRow } from '../DataTable/dnd/dataTableDnd.utils'
3
15
 
4
16
  const defaultSetlists = [
5
17
  {
@@ -177,7 +189,7 @@ export default {
177
189
  export const Default = {
178
190
  render: (args) => {
179
191
  const [selectedSetlistId, setSelectedSetlistId] = useState(
180
- defaultSetlists[0].id
192
+ defaultSetlists[0].id,
181
193
  )
182
194
 
183
195
  return (
@@ -228,68 +240,291 @@ export const Empty = {
228
240
  },
229
241
  }
230
242
 
231
- export const Droppable = {
232
- render: (args) => {
233
- const [activeDropSetlistId, setActiveDropSetlistId] = useState(null)
234
- const [lastDropSetlistId, setLastDropSetlistId] = useState(null)
243
+ const trackRows = [
244
+ {
245
+ id: 'track-1',
246
+ thumb:
247
+ 'https://d2.moises.ai/v3/download/moises-production--tasks/operations/FILEMETADATA_A/81735fa7-bba1-42f1-bd20-7208d3d77fc2/cover.jpeg',
248
+ title: 'Come Together',
249
+ artist: 'The Beatles',
250
+ RECENT: 'Nov 11, 2025',
251
+ BPM: 110,
252
+ KEY: 'G',
253
+ DURATION: '03:48',
254
+ },
255
+ {
256
+ id: 'track-2',
257
+ title: 'Let It Be',
258
+ type: 'studio',
259
+ artist: 'The Beatles',
260
+ RECENT: 'Nov 11, 2025',
261
+ BPM: 110,
262
+ KEY: 'E♭m',
263
+ DURATION: '03:48',
264
+ },
265
+ {
266
+ id: 'track-3',
267
+ title: 'Here Comes The Sun',
268
+ artist: 'The Beatles',
269
+ type: 'stems',
270
+ RECENT: 'Dec 02, 2025',
271
+ BPM: 129,
272
+ KEY: 'C#',
273
+ DURATION: '03:05',
274
+ },
275
+ {
276
+ id: 'track-4',
277
+ title: 'Yesterday',
278
+ type: 'voice',
279
+ artist: 'The Beatles',
280
+ RECENT: 'Jan 15, 2025',
281
+ BPM: 95,
282
+ KEY: 'F',
283
+ DURATION: '02:05',
284
+ },
285
+ {
286
+ id: 'track-5',
287
+ title: 'Help!',
288
+ type: 'master',
289
+ artist: 'The Beatles',
290
+ RECENT: 'Oct 30, 2025',
291
+ BPM: 184,
292
+ KEY: 'Bm',
293
+ DURATION: '02:18',
294
+ },
295
+ ]
296
+
297
+ const trackColumns = [
298
+ { id: 'title', label: 'Title', sortable: true },
299
+ { id: 'RECENT', label: 'Created', sortable: true },
300
+ { id: 'BPM', label: 'BPM', sortable: true },
301
+ { id: 'KEY', label: 'Key', sortable: true },
302
+ { id: 'DURATION', label: 'Duration', sortable: true },
303
+ ]
304
+
305
+ const dndSetlists = defaultSetlists.slice(0, 4)
306
+
307
+ export const DragFromDataTable = {
308
+ render: () => {
309
+ const [selectedRowIds, setSelectedRowIds] = useState([])
310
+ const [activeDragContext, setActiveDragContext] = useState(null)
311
+ const [dropLog, setDropLog] = useState([])
312
+
313
+ const sensors = useSensors(
314
+ useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
315
+ )
316
+
317
+ const normalizedRows = useMemo(
318
+ () => trackRows.map((row, index) => toDragRow(row, index)),
319
+ [],
320
+ )
321
+
322
+ const selectedSet = useMemo(
323
+ () => new Set(selectedRowIds.map(String)),
324
+ [selectedRowIds],
325
+ )
326
+
327
+ const handleDragStart = useCallback(
328
+ (event) => {
329
+ const activeId = String(event.active?.id ?? '')
330
+ const dragRow = normalizedRows.find((r) => r.id === activeId)
331
+ if (!dragRow) return
332
+
333
+ const isDraggedRowSelected = selectedSet.has(activeId)
334
+ const selectedRows = isDraggedRowSelected
335
+ ? normalizedRows.filter((r) => selectedSet.has(r.id))
336
+ : [dragRow]
337
+
338
+ setActiveDragContext({
339
+ row: dragRow,
340
+ selectedRows,
341
+ isDraggedRowSelected,
342
+ })
343
+ },
344
+ [normalizedRows, selectedSet],
345
+ )
346
+
347
+ const handleDragEnd = useCallback(
348
+ (event) => {
349
+ const { active, over } = event
350
+
351
+ if (over && activeDragContext) {
352
+ const setlistId = String(over.id).replace('setlist-drop-', '')
353
+ const setlist = dndSetlists.find((s) => s.id === setlistId)
354
+
355
+ if (setlist) {
356
+ const trackNames = activeDragContext.selectedRows
357
+ .map((r) => r.title)
358
+ .join(', ')
359
+ setDropLog((prev) => [
360
+ {
361
+ id: Date.now(),
362
+ text: `"${trackNames}" → "${setlist.label}"`,
363
+ },
364
+ ...prev.slice(0, 4),
365
+ ])
366
+ }
367
+ }
368
+
369
+ setActiveDragContext(null)
370
+ },
371
+ [activeDragContext],
372
+ )
373
+
374
+ const handleDragCancel = useCallback(() => {
375
+ setActiveDragContext(null)
376
+ }, [])
235
377
 
236
378
  return (
237
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
238
- <div
239
- draggable
240
- onDragStart={(event) => {
241
- event.dataTransfer.setData('application/x-track-id', 'track-01')
242
- }}
243
- style={{
244
- width: 'fit-content',
245
- border: '1px dashed var(--accent-9)',
246
- borderRadius: 8,
247
- padding: '8px 12px',
248
- cursor: 'grab',
249
- color: 'var(--accent-11)',
250
- }}
251
- >
252
- Drag me and drop on a setlist
253
- </div>
379
+ <DndContext
380
+ sensors={sensors}
381
+ collisionDetection={pointerWithin}
382
+ onDragStart={handleDragStart}
383
+ onDragEnd={handleDragEnd}
384
+ onDragCancel={handleDragCancel}
385
+ >
386
+ <div style={{ display: 'flex', gap: 24, height: '100%' }}>
387
+ <div
388
+ style={{
389
+ flex: 1,
390
+ maxHeight: 400,
391
+ overflow: 'auto',
392
+ borderRadius: 8,
393
+ }}
394
+ >
395
+ <DataTable
396
+ columns={trackColumns}
397
+ data={trackRows}
398
+ selectedRowIds={selectedRowIds}
399
+ selectedFilesLabel={
400
+ selectedRowIds.length === 1
401
+ ? '1 file selected'
402
+ : `${selectedRowIds.length} files selected`
403
+ }
404
+ onSelectRow={(id, checked) =>
405
+ setSelectedRowIds((prev) =>
406
+ checked
407
+ ? prev.includes(id)
408
+ ? prev
409
+ : [...prev, id]
410
+ : prev.filter((x) => x !== id),
411
+ )
412
+ }
413
+ onSelectAll={(checked) =>
414
+ setSelectedRowIds(checked ? trackRows.map((r) => r.id) : [])
415
+ }
416
+ sorting={{ field: 'RECENT', sort: 'DESC' }}
417
+ rowDraggable
418
+ renderDndContext={false}
419
+ getDragPreviewLabel={(row, selectedRows) =>
420
+ selectedRows.length > 1
421
+ ? `${selectedRows.length} files`
422
+ : (row.trackName ?? row.title)
423
+ }
424
+ />
425
+ </div>
426
+
427
+ <div
428
+ style={{
429
+ width: 260,
430
+ display: 'flex',
431
+ flexDirection: 'column',
432
+ gap: 4,
433
+ }}
434
+ >
435
+ <div
436
+ style={{
437
+ fontSize: 11,
438
+ fontWeight: 600,
439
+ textTransform: 'uppercase',
440
+ letterSpacing: 1,
441
+ color: 'var(--neutral-alpha-8)',
442
+ padding: '0 12px 8px',
443
+ }}
444
+ >
445
+ Drop on a setlist
446
+ </div>
254
447
 
255
- <div style={{ color: 'var(--neutral-alpha-11)', fontSize: 12 }}>
256
- {lastDropSetlistId
257
- ? `Last dropped on: ${lastDropSetlistId}`
258
- : 'No drop yet'}
448
+ {dndSetlists.map((setlist) => (
449
+ <SetlistItem
450
+ key={setlist.id}
451
+ setlistId={setlist.id}
452
+ text={setlist.label}
453
+ subtitle={setlist.subtitle}
454
+ dndDroppableId={`setlist-drop-${setlist.id}`}
455
+ />
456
+ ))}
457
+
458
+ {dropLog.length > 0 && (
459
+ <div
460
+ style={{
461
+ marginTop: 16,
462
+ padding: '0 12px',
463
+ display: 'flex',
464
+ flexDirection: 'column',
465
+ gap: 4,
466
+ }}
467
+ >
468
+ <div
469
+ style={{
470
+ fontSize: 11,
471
+ fontWeight: 600,
472
+ textTransform: 'uppercase',
473
+ letterSpacing: 1,
474
+ color: 'var(--neutral-alpha-8)',
475
+ paddingBottom: 4,
476
+ }}
477
+ >
478
+ Drop log
479
+ </div>
480
+ {dropLog.map((entry) => (
481
+ <div
482
+ key={entry.id}
483
+ style={{
484
+ fontSize: 12,
485
+ color: 'var(--neutral-alpha-11)',
486
+ padding: '4px 0',
487
+ borderBottom: '1px solid var(--neutral-alpha-3)',
488
+ }}
489
+ >
490
+ {entry.text}
491
+ </div>
492
+ ))}
493
+ </div>
494
+ )}
495
+ </div>
259
496
  </div>
260
497
 
261
- <SetlistList
262
- {...args}
263
- setlists={defaultSetlists}
264
- activeDropSetlistId={activeDropSetlistId}
265
- onSetlistDragOver={(setlistId, event) => {
266
- event.preventDefault()
267
- setActiveDropSetlistId(setlistId)
268
- args.onSetlistDragOver?.(setlistId, event)
269
- }}
270
- onSetlistDragEnter={(setlistId, event) => {
271
- setActiveDropSetlistId(setlistId)
272
- args.onSetlistDragEnter?.(setlistId, event)
273
- }}
274
- onSetlistDragLeave={(setlistId, event) => {
275
- setActiveDropSetlistId((current) =>
276
- current === setlistId ? null : current,
277
- )
278
- args.onSetlistDragLeave?.(setlistId, event)
279
- }}
280
- onSetlistDrop={(setlistId, event) => {
281
- event.preventDefault()
282
- setLastDropSetlistId(setlistId)
283
- setActiveDropSetlistId(null)
284
- args.onSetlistDrop?.(setlistId, event)
285
- }}
286
- />
287
- </div>
498
+ <DragOverlay modifiers={[snapCenterToCursor]}>
499
+ {activeDragContext ? (
500
+ <DataTableDragOverlay
501
+ model={activeDragContext}
502
+ getDragPreviewLabel={(row, selectedRows) =>
503
+ selectedRows.length > 1
504
+ ? `${selectedRows.length} files`
505
+ : (row.trackName ?? row.title)
506
+ }
507
+ />
508
+ ) : null}
509
+ </DragOverlay>
510
+ </DndContext>
288
511
  )
289
512
  },
290
- args: {
291
- setlists: defaultSetlists,
292
- droppable: true,
293
- collapsed: false,
513
+ parameters: {
514
+ layout: 'fullscreen',
294
515
  },
516
+ decorators: [
517
+ (Story) => (
518
+ <div
519
+ style={{
520
+ width: '100%',
521
+ maxWidth: 1200,
522
+ margin: '24px auto',
523
+ padding: '0 24px',
524
+ }}
525
+ >
526
+ <Story />
527
+ </div>
528
+ ),
529
+ ],
295
530
  }
@@ -5,6 +5,7 @@
5
5
  bottom: 16px;
6
6
  display: flex;
7
7
  flex-direction: column;
8
+ align-items: center;
8
9
  gap: 8px;
9
10
  width: min(360px, calc(100vw - 24px));
10
11
  margin: 0;
@@ -16,6 +17,8 @@
16
17
 
17
18
  .toastRoot {
18
19
  display: block;
20
+ width: fit-content;
21
+ max-width: 100%;
19
22
  padding: 0;
20
23
  box-shadow: 0 12px 24px -16px rgba(0, 0, 0, 0.6);
21
24
  }
@@ -26,8 +29,8 @@
26
29
  }
27
30
 
28
31
  .toastCallout {
29
- width: 100%;
30
32
  min-width: 0;
31
33
  border-radius: 8px;
32
34
  padding-right: 8px;
35
+ word-break: break-word;
33
36
  }