@moises.ai/design-system 3.7.2 → 3.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moises.ai/design-system",
3
- "version": "3.7.2",
3
+ "version": "3.8.0",
4
4
  "description": "Design System package based on @radix-ui/themes with custom defaults",
5
5
  "private": false,
6
6
  "type": "module",
@@ -0,0 +1,71 @@
1
+ import { useCallback } from 'react'
2
+ import { Flex } from '@radix-ui/themes'
3
+ import { MinusIcon, PlusIcon } from '../../icons'
4
+ import { IconButton } from '../IconButton/IconButton'
5
+ import { Text } from '../Text/Text'
6
+
7
+ export const NumberPicker = ({
8
+ label,
9
+ value = 0,
10
+ min = 0,
11
+ max = 10,
12
+ onChange,
13
+ }) => {
14
+ const onIncrement = useCallback((e) => {
15
+ e.stopPropagation()
16
+ if (max != null && value >= max) return
17
+ onChange(value + 1)
18
+ }, [max, onChange, value])
19
+
20
+ const onDecrement = useCallback((e) => {
21
+ e.stopPropagation()
22
+ if (min != null && value <= min) return
23
+ onChange(value - 1)
24
+ }, [min, onChange, value])
25
+
26
+ return (
27
+ <Flex direction="row" gap="1" align="center" justify="center">
28
+ {label && (
29
+ <Text size="2" weight="medium">
30
+ {label}
31
+ </Text>
32
+ )}
33
+ <Flex direction="row" gap="1" align="center" justify="center">
34
+ <IconButton
35
+ type="button"
36
+ aria-label="increment"
37
+ onClick={onDecrement}
38
+ style={{
39
+ backgroundColor: 'var(--neutral-alpha-3 )',
40
+ }}
41
+ >
42
+ <MinusIcon width={16} height={16} />
43
+ </IconButton>
44
+
45
+ <Flex
46
+ align="center"
47
+ justify="center"
48
+ style={{
49
+ height: '24px',
50
+ minWidth: '42px',
51
+ borderRadius: 'var(--radius-2)',
52
+ backgroundColor: 'var(--neutral-alpha-3 )',
53
+ }}
54
+ >
55
+ <Text size="2">{value > 0 ? `+${value}` : value}</Text>
56
+ </Flex>
57
+
58
+ <IconButton
59
+ type="button"
60
+ aria-label="decrement"
61
+ onClick={onIncrement}
62
+ style={{
63
+ backgroundColor: 'var(--neutral-alpha-3 )',
64
+ }}
65
+ >
66
+ <PlusIcon width={16} height={16} />
67
+ </IconButton>
68
+ </Flex>
69
+ </Flex>
70
+ )
71
+ }
@@ -0,0 +1,63 @@
1
+ import { useState } from 'react'
2
+ import { NumberPicker } from './NumberPicker'
3
+
4
+ export default {
5
+ title: 'Components/NumberPicker',
6
+ component: NumberPicker,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ label: { control: 'text' },
10
+ value: { control: 'number' },
11
+ min: { control: 'number' },
12
+ max: { control: 'number' },
13
+ },
14
+ }
15
+
16
+ export const Default = {
17
+ args: {
18
+ label: 'Number',
19
+ value: 0,
20
+ min: -10,
21
+ max: 10,
22
+ },
23
+ render: (args) => {
24
+ const [value, setValue] = useState(args.value)
25
+ const handleChange = (newValue) => {
26
+ console.log('handleChange. new value:', newValue)
27
+ setValue(parseInt(newValue))
28
+ }
29
+ return (
30
+ <NumberPicker
31
+ label={args.label}
32
+ value={value}
33
+ min={args.min}
34
+ max={args.max}
35
+ onChange={handleChange}
36
+ />
37
+ )
38
+ },
39
+ }
40
+
41
+ export const WithoutLabel = {
42
+ args: {
43
+ value: 0,
44
+ min: -10,
45
+ max: 10,
46
+ },
47
+ render: (args) => {
48
+ const [value, setValue] = useState(args.value)
49
+ const handleChange = (newValue) => {
50
+ console.log('handleChange. new value:', newValue)
51
+ setValue(parseInt(newValue))
52
+ }
53
+ return (
54
+ <NumberPicker
55
+ label={args.label}
56
+ value={value}
57
+ min={args.min}
58
+ max={args.max}
59
+ onChange={handleChange}
60
+ />
61
+ )
62
+ },
63
+ }
@@ -0,0 +1,146 @@
1
+ import { Flex, Text, Separator, Spinner, Button } from '@radix-ui/themes'
2
+ import { useState, useContext, useCallback, useMemo } from 'react'
3
+ import { ArrowLeftIcon, NoMusicIcon, ChevronDownIcon, ChevronUpIcon, InfoIcon } from '../../../icons'
4
+ import { ProjectsListContext } from '../context'
5
+ import { ProductsBrandPattern } from '../../ProductsBrandPattern/ProductsBrandPattern'
6
+ import styles from './ProjectItem.module.css'
7
+
8
+ const ProjectItem = ({
9
+ id,
10
+ title,
11
+ artist,
12
+ cover,
13
+ type,
14
+ tracks: allTracks = [],
15
+ loading = false,
16
+ failed = false,
17
+ children,
18
+ ...props
19
+ }) => {
20
+ const {
21
+ projects,
22
+ onInsertAllTracks,
23
+ onProjectClick,
24
+ openedItem,
25
+ setOpenedItem,
26
+ } = useContext(ProjectsListContext)
27
+ const [showAllTracks, setShowAllTracks] = useState(false)
28
+
29
+ const tracks = useMemo(() => {
30
+ return showAllTracks ? allTracks : allTracks.slice(0, 6)
31
+ }, [allTracks, showAllTracks])
32
+
33
+ const handleClickProject = useCallback(() => {
34
+ if (failed) return
35
+ setShowAllTracks(false)
36
+ const project = projects.find((p) => p.id === id)
37
+
38
+ if (openedItem === id) {
39
+ setOpenedItem(null)
40
+ onProjectClick?.(project, false)
41
+ } else {
42
+ setOpenedItem(id)
43
+ onProjectClick?.(project, true)
44
+ }
45
+ }, [id, openedItem, setOpenedItem, onProjectClick, projects, failed])
46
+
47
+ const handleClickShowAllTracks = useCallback((e) => {
48
+ e.preventDefault()
49
+ e.stopPropagation()
50
+ setShowAllTracks(!showAllTracks)
51
+ }, [showAllTracks])
52
+
53
+ const handleInsertAllTracks = useCallback((e) => {
54
+ e.stopPropagation()
55
+ onInsertAllTracks?.(allTracks)
56
+ }, [onInsertAllTracks, allTracks])
57
+
58
+ return (
59
+ <Flex
60
+ direction="column"
61
+ gap="2"
62
+ p="1"
63
+ className={`${styles.item} ${openedItem === id ? styles.opened : ''} ${failed ? styles.failed : ''}`}
64
+ onClick={handleClickProject}
65
+ {...props}
66
+ >
67
+ <Flex direction="row" gap="3">
68
+ <Flex className={styles.projectCover}>
69
+ <ProductsBrandPattern type={type} cover={cover} size="48px" />
70
+ </Flex>
71
+
72
+ <Flex
73
+ direction="column"
74
+ gap="1"
75
+ align="start"
76
+ justify="center"
77
+ style={{ flex: 1, minWidth: 0 }}
78
+ >
79
+ <Text size="2" asChild>
80
+ <span className={styles.projectTitle}>
81
+ {title}
82
+ </span>
83
+ </Text>
84
+
85
+ {failed ? (
86
+ <Flex size="1" gap="1" className={styles.projectFailed}>
87
+ <InfoIcon width={16} height={16} />
88
+ <Text size="1">Failed</Text>
89
+ </Flex>
90
+ ) : artist && (
91
+ <Text size="1" asChild>
92
+ <span className={styles.projectArtist}>
93
+ {artist}
94
+ </span>
95
+ </Text>
96
+ )}
97
+ </Flex>
98
+ </Flex>
99
+
100
+ {openedItem === id && (
101
+ <Flex direction="column">
102
+ {allTracks.length === 0 && !loading && (
103
+ <Flex direction="row" gap="3" p="5" pt="2" align="center" justify="center">
104
+ <NoMusicIcon width={16} height={16} style={{ color: 'var(--neutral-alpha-10)' }} />
105
+ <Text size="2" style={{ color: 'var(--neutral-alpha-10)' }}>This file has no tracks</Text>
106
+ </Flex>
107
+ )}
108
+
109
+ {loading && (
110
+ <Flex direction="row" gap="3" p="5" align="center" justify="center">
111
+ <Spinner width={16} height={16} style={{ color: 'var(--neutral-alpha-10)' }} />
112
+ <Text size="2" style={{ color: 'var(--neutral-alpha-10)' }}>Loading tracks...</Text>
113
+ </Flex>
114
+ )}
115
+
116
+ {allTracks.length > 0 && (
117
+ <Flex direction="column" gap="1">
118
+ {typeof children === 'function' ? children(tracks) : children}
119
+
120
+ <Flex direction="row" gap="3" p="2" align="center" justify="center">
121
+ <Button variant="ghost" color="gray" style={{ flex: '1' }}
122
+ onClick={handleInsertAllTracks}
123
+ >
124
+ <ArrowLeftIcon width={16} height={16} /> Insert all
125
+ </Button>
126
+
127
+ {allTracks.length > 6 && (
128
+ <>
129
+ <Separator orientation="vertical" />
130
+
131
+ <Button variant="ghost" color="gray" style={{ flex: '1' }} onClick={handleClickShowAllTracks}>
132
+ {showAllTracks ? 'Show less' : 'Show more'}
133
+ {showAllTracks ? <ChevronUpIcon width={16} height={16} /> : <ChevronDownIcon width={16} height={16} />}
134
+ </Button>
135
+ </>
136
+ )}
137
+ </Flex>
138
+ </Flex>
139
+ )}
140
+ </Flex>
141
+ )}
142
+ </Flex>
143
+ )
144
+ }
145
+
146
+ export default ProjectItem
@@ -0,0 +1,53 @@
1
+ .projectTitle {
2
+ color: var(--neutral-12);
3
+ padding-right: var(--space-2);
4
+ display: block;
5
+ width: 100%;
6
+ min-width: 0;
7
+ overflow: hidden;
8
+ text-overflow: ellipsis;
9
+ white-space: nowrap;
10
+ }
11
+
12
+ .projectArtist {
13
+ color: var(--neutral-alpha-9);
14
+ display: block;
15
+ width: 100%;
16
+ min-width: 0;
17
+ overflow: hidden;
18
+ text-overflow: ellipsis;
19
+ white-space: nowrap;
20
+ }
21
+
22
+ .projectFailed {
23
+ color: var(--error-alpha-11);
24
+ }
25
+
26
+ .projectCover {
27
+ width: 48px;
28
+ height: 48px;
29
+ flex: 0 0 48px;
30
+ border-radius: var(--radius-3);
31
+ overflow: hidden;
32
+ }
33
+
34
+ .projectCover img {
35
+ width: 100%;
36
+ height: 100%;
37
+ object-fit: cover;
38
+ pointer-events: none;
39
+ }
40
+
41
+ .item:not(.failed) {
42
+ cursor: pointer;
43
+ border-radius: var(--radius-4);
44
+
45
+ &.opened {
46
+ background: var(--neutral-alpha-2);
47
+ box-shadow: 0 24px 24px -16px rgba(0, 0, 0, 0.40);
48
+ }
49
+
50
+ &:hover:not(.opened) {
51
+ background: rgba(216, 244, 246, 0.04);
52
+ }
53
+ }
@@ -0,0 +1,52 @@
1
+ import { Flex, Text } from '@radix-ui/themes'
2
+ import { useCallback, useContext } from 'react'
3
+ import { ArrowLeftIcon } from '../../../icons'
4
+ import { ProjectsListContext } from '../context'
5
+ import { getIconFromTrackId } from '../utils'
6
+ import styles from './ProjectTrack.module.css'
7
+
8
+ const ProjectTrack = ({
9
+ ...props
10
+ }) => {
11
+ const { onClickTrack } = useContext(ProjectsListContext)
12
+ const { id, title } = props
13
+
14
+ const handleClickTrack = useCallback((e) => {
15
+ e.stopPropagation()
16
+ onClickTrack?.(props)
17
+ }, [onClickTrack, props])
18
+
19
+ return (
20
+ <Flex direction="column" gap="1">
21
+ <Flex
22
+ direction="row"
23
+ gap="3"
24
+ className={styles.itemTrack}
25
+ onClick={handleClickTrack}
26
+ >
27
+ <div className={styles.icon}>
28
+ <Flex className={styles.iconTrack}>
29
+ {getIconFromTrackId(id)}
30
+ </Flex>
31
+ <ArrowLeftIcon width={16} height={16} className={styles.iconArrowLeft} />
32
+ </div>
33
+
34
+ <Flex
35
+ direction="column"
36
+ gap="1"
37
+ align="start"
38
+ justify="center"
39
+ style={{ flex: 1, minWidth: 0 }}
40
+ >
41
+ <Text size="2" asChild>
42
+ <span className={styles.trackTitle}>
43
+ {title}
44
+ </span>
45
+ </Text>
46
+ </Flex>
47
+ </Flex>
48
+ </Flex>
49
+ )
50
+ }
51
+
52
+ export default ProjectTrack
@@ -0,0 +1,42 @@
1
+ .itemTrack {
2
+ cursor: pointer;
3
+ border-radius: var(--radius-3);
4
+
5
+ .iconArrowLeft {
6
+ display: none;
7
+ }
8
+
9
+ &:hover {
10
+ background: var(--neutral-alpha-2);
11
+ box-shadow: 0 24px 24px -16px rgba(0, 0, 0, 0.40);
12
+
13
+ .iconArrowLeft {
14
+ display: block;
15
+ }
16
+
17
+ .iconTrack {
18
+ display: none;
19
+ }
20
+ }
21
+ }
22
+
23
+ .trackTitle {
24
+ color: var(--neutral-alpha-12);
25
+ display: block;
26
+ width: 100%;
27
+ min-width: 0;
28
+ overflow: hidden;
29
+ text-overflow: ellipsis;
30
+ white-space: nowrap;
31
+ }
32
+
33
+ .icon {
34
+ width: 48px;
35
+ height: 48px;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ background: var(--neutral-alpha-2);
40
+ border-radius: var(--radius-3);
41
+ color: var(--neutral-alpha-10);
42
+ }
@@ -0,0 +1,88 @@
1
+ import { Flex, Text } from '@radix-ui/themes'
2
+ import { useCallback, useContext, useMemo, useState } from 'react'
3
+ import { ArrowLeftIcon } from '../../../icons'
4
+ import { ProjectsListContext } from '../context'
5
+ import { NumberPicker } from '../../NumberPicker/NumberPicker'
6
+ import { UserIcon } from '../../../icons'
7
+ import styles from './ProjectTrackVoice.module.css'
8
+
9
+ const ProjectTrackVoice = ({
10
+ ...props
11
+ }) => {
12
+ const { onClickTrack } = useContext(ProjectsListContext)
13
+ const { id, title, cover, transfers, audioUrl, onChangeTransfer } = props
14
+
15
+ const hasTransfers = useMemo(() => transfers && transfers.length > 0, [transfers])
16
+
17
+ const [pitchShiftSelected, setpitchShiftSelected] = useState(hasTransfers ? parseInt(transfers?.[0]?.pitchShift ?? 0) : 0)
18
+ const min = useMemo(() => hasTransfers ? Math.min(...transfers?.map((transfer) => parseInt(transfer.pitchShift))) : 0, [transfers])
19
+ const max = useMemo(() => hasTransfers ? Math.max(...transfers?.map((transfer) => parseInt(transfer.pitchShift))) : 0, [transfers])
20
+
21
+ const getAudioUrl = useCallback((pitchShift) => {
22
+ return hasTransfers ? transfers?.find((transfer) => parseInt(transfer.pitchShift) === pitchShift)?.audioUrl : audioUrl
23
+ }, [hasTransfers, transfers, audioUrl])
24
+
25
+ const handleChangeTransfer = useCallback((value) => {
26
+ setpitchShiftSelected(value)
27
+ onChangeTransfer?.({
28
+ id,
29
+ title,
30
+ audioUrl: getAudioUrl(value),
31
+ })
32
+ }, [onChangeTransfer, id, title, transfers])
33
+
34
+ const handleClickTrack = useCallback((e) => {
35
+ e.stopPropagation()
36
+ onClickTrack?.({
37
+ id,
38
+ title,
39
+ audioUrl: getAudioUrl(pitchShiftSelected),
40
+ })
41
+ }, [onClickTrack, id, title, getAudioUrl, pitchShiftSelected])
42
+
43
+ return (
44
+ <Flex direction="column" gap="1">
45
+ <Flex
46
+ direction="row"
47
+ gap="3"
48
+ className={styles.itemTrack}
49
+ onClick={handleClickTrack}
50
+ >
51
+ <div className={styles.cover}>
52
+ <Flex className={styles.coverImage}>
53
+ {cover
54
+ ? <img src={cover} alt={title} width={48} height={48} />
55
+ : <UserIcon width={16} height={16} />
56
+ }
57
+ </Flex>
58
+ <ArrowLeftIcon width={16} height={16} className={styles.iconArrowLeft} />
59
+ </div>
60
+
61
+ <Flex
62
+ direction="column"
63
+ gap="1"
64
+ align="start"
65
+ justify="center"
66
+ style={{ flex: 1, minWidth: 0 }}
67
+ >
68
+ <Text size="2" asChild>
69
+ <span className={styles.trackTitle}>
70
+ {title}
71
+ </span>
72
+ </Text>
73
+ </Flex>
74
+
75
+ {hasTransfers && (
76
+ <NumberPicker
77
+ min={min}
78
+ max={max}
79
+ value={pitchShiftSelected}
80
+ onChange={handleChangeTransfer}
81
+ />
82
+ )}
83
+ </Flex>
84
+ </Flex>
85
+ )
86
+ }
87
+
88
+ export default ProjectTrackVoice
@@ -1,43 +1,7 @@
1
- .projectTitle {
2
- color: var(--neutral-12);
3
- }
4
-
5
- .projectArtist {
6
- color: var(--neutral-alpha-9);
7
- }
8
-
9
- .projectCover {
10
- width: 48px;
11
- height: 48px;
12
- flex: 0 0 48px;
13
- border-radius: var(--radius-3);
14
- overflow: hidden;
15
- }
16
-
17
- .projectCover img {
18
- width: 100%;
19
- height: 100%;
20
- object-fit: cover;
21
- pointer-events: none;
22
- }
23
-
24
- .item {
25
- cursor: pointer;
26
- border-radius: var(--radius-4);
27
-
28
- &.opened {
29
- background: var(--neutral-alpha-2);
30
- box-shadow: 0 24px 24px -16px rgba(0, 0, 0, 0.40);
31
- }
32
-
33
- &:hover:not(.opened) {
34
- background: rgba(216, 244, 246, 0.04);
35
- }
36
- }
37
-
38
1
  .itemTrack {
39
2
  cursor: pointer;
40
3
  border-radius: var(--radius-3);
4
+ overflow: hidden;
41
5
 
42
6
  .iconArrowLeft {
43
7
  display: none;
@@ -51,7 +15,7 @@
51
15
  display: block;
52
16
  }
53
17
 
54
- .iconTrack {
18
+ .coverImage {
55
19
  display: none;
56
20
  }
57
21
  }
@@ -59,9 +23,15 @@
59
23
 
60
24
  .trackTitle {
61
25
  color: var(--neutral-alpha-12);
26
+ display: block;
27
+ width: 100%;
28
+ min-width: 0;
29
+ overflow: hidden;
30
+ text-overflow: ellipsis;
31
+ white-space: nowrap;
62
32
  }
63
33
 
64
- .icon {
34
+ .cover {
65
35
  width: 48px;
66
36
  height: 48px;
67
37
  display: flex;
@@ -69,4 +39,22 @@
69
39
  justify-content: center;
70
40
  background: var(--neutral-alpha-2);
71
41
  border-radius: var(--radius-3);
72
- }
42
+ }
43
+
44
+ .coverImage {
45
+ width: 48px;
46
+ height: 48px;
47
+ border-radius: var(--radius-3);
48
+ overflow: hidden;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ color: var(--neutral-alpha-10);
53
+
54
+ img {
55
+ width: 100%;
56
+ height: 100%;
57
+ object-fit: cover;
58
+ pointer-events: none;
59
+ }
60
+ }