@moises.ai/design-system 4.16.1 → 4.16.3

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.
@@ -0,0 +1,70 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import WavesurferPlayer from '@wavesurfer/react'
3
+
4
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max)
5
+
6
+ export function PlayerSliderWaveform({
7
+ audio,
8
+ progress = 0,
9
+ currentTime = 0,
10
+ duration = 0,
11
+ }) {
12
+ const [isSafari, setIsSafari] = useState(false)
13
+ const waveSurferRef = useRef(null)
14
+
15
+ useEffect(() => {
16
+ if (typeof navigator !== 'undefined') {
17
+ setIsSafari(/^((?!chrome|android).)*safari/i.test(navigator.userAgent))
18
+ }
19
+ }, [])
20
+
21
+ const syncProgress = useCallback(
22
+ (ws = waveSurferRef.current) => {
23
+ if (!ws) return
24
+
25
+ const safeProgress = clamp(progress, 0, 100)
26
+ const waveformDuration = ws.getDuration?.()
27
+ const targetTime =
28
+ Number.isFinite(waveformDuration) && waveformDuration > 0
29
+ ? (safeProgress / 100) * waveformDuration
30
+ : currentTime
31
+
32
+ if (!Number.isFinite(targetTime)) return
33
+
34
+ ws.setTime(targetTime)
35
+ },
36
+ [currentTime, progress],
37
+ )
38
+
39
+ const handleReady = useCallback(
40
+ (ws) => {
41
+ waveSurferRef.current = ws
42
+ syncProgress(ws)
43
+ },
44
+ [syncProgress],
45
+ )
46
+
47
+ useEffect(() => {
48
+ syncProgress()
49
+ }, [duration, syncProgress])
50
+
51
+ if (!audio) return null
52
+
53
+ return (
54
+ <WavesurferPlayer
55
+ url={audio}
56
+ waveColor="rgba(255, 255, 255, 0.22)"
57
+ progressColor="rgba(0, 227, 254, 1)"
58
+ normalize
59
+ barWidth={2}
60
+ barHeight={1}
61
+ cursorWidth={0}
62
+ height={56}
63
+ interact={false}
64
+ onReady={handleReady}
65
+ backend={isSafari ? 'WebAudio' : 'MediaElement'}
66
+ />
67
+ )
68
+ }
69
+
70
+ PlayerSliderWaveform.displayName = 'PlayerSliderWaveform'
@@ -0,0 +1,203 @@
1
+ import classNames from 'classnames'
2
+ import { Slider as SliderRadix } from 'radix-ui'
3
+ import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
4
+ import { Tooltip } from '../Tooltip/Tooltip'
5
+ import styles from './SliderLibrary.module.css'
6
+
7
+ /**
8
+ * @typedef {Object} SliderLibraryProps
9
+ * @property {number} value - Currently selected value (controlled).
10
+ * @property {(value: number) => void} [onValueChange] - Fires whenever the user drags,
11
+ * keys the thumb or scrolls over the slider. Receives a single number (not an array).
12
+ * @property {number} [min=0] - Minimum allowed value.
13
+ * @property {number} [max=100] - Maximum allowed value.
14
+ * @property {number} [step=1] - Increment step. Used by keyboard, drag and wheel.
15
+ * @property {boolean} [disabled=false] - Disables interaction and dims the slider.
16
+ * @property {boolean} [showTooltip=true] - Renders the DS Tooltip below the thumb on
17
+ * hover/focus/drag with the current value.
18
+ * @property {(value: number) => React.ReactNode} [formatValue] - Formats the value for
19
+ * display in the tooltip. Defaults to the raw number.
20
+ * @property {string} [aria-label] - Accessible label, recommended when no visual label exists.
21
+ * @property {string} [className] - Extra class on the root.
22
+ */
23
+
24
+ /**
25
+ * Minimalist horizontal slider used in Library player surfaces.
26
+ *
27
+ * Visuals (from Figma):
28
+ * - 4px track with `--neutral-alpha-3` background.
29
+ * - Range fill in `--aqua-alpha-8` (cyan).
30
+ * - 18x10 white thumb with subtle border, rounded-full.
31
+ * - DS `Tooltip` anchored below the thumb shows the current value, following the thumb during drag.
32
+ *
33
+ * Behavior:
34
+ * - Drag: pointer/touch. Tooltip stays open and follows the thumb continuously.
35
+ * - Keyboard (via Radix Slider primitives): arrows/Home/End/PageUp/PageDown.
36
+ * - Wheel: scrolling over the slider increments/decrements by `step`.
37
+ * - Focus ring: appears only when the thumb is focused via keyboard, never on click/drag.
38
+ *
39
+ * @example
40
+ * const [volume, setVolume] = useState(50)
41
+ * <SliderLibrary
42
+ * value={volume}
43
+ * onValueChange={setVolume}
44
+ * formatValue={(v) => `${v}%`}
45
+ * aria-label="Volume"
46
+ * />
47
+ *
48
+ * @type {React.ForwardRefExoticComponent<SliderLibraryProps & React.RefAttributes<HTMLSpanElement>>}
49
+ */
50
+ export const SliderLibrary = forwardRef(function SliderLibrary(
51
+ {
52
+ value,
53
+ onValueChange,
54
+ min = 0,
55
+ max = 100,
56
+ step = 1,
57
+ disabled = false,
58
+ showTooltip = true,
59
+ formatValue,
60
+ className,
61
+ ...props
62
+ },
63
+ ref,
64
+ ) {
65
+ const ariaLabel = props['aria-label']
66
+ const internalRef = useRef(null)
67
+ const lastInteractionRef = useRef(null)
68
+ const [keyboardFocus, setKeyboardFocus] = useState(false)
69
+ const [isHovering, setIsHovering] = useState(false)
70
+ const [isDragging, setIsDragging] = useState(false)
71
+
72
+ const stateRef = useRef({ value, min, max, step, disabled, onValueChange })
73
+ stateRef.current = { value, min, max, step, disabled, onValueChange }
74
+
75
+ useEffect(() => {
76
+ const onKey = () => {
77
+ lastInteractionRef.current = 'keyboard'
78
+ }
79
+ const onPointer = () => {
80
+ lastInteractionRef.current = 'pointer'
81
+ }
82
+ window.addEventListener('keydown', onKey, true)
83
+ window.addEventListener('pointerdown', onPointer, true)
84
+ return () => {
85
+ window.removeEventListener('keydown', onKey, true)
86
+ window.removeEventListener('pointerdown', onPointer, true)
87
+ }
88
+ }, [])
89
+
90
+ useEffect(() => {
91
+ const node = internalRef.current
92
+ if (!node) return undefined
93
+
94
+ const handleWheel = (event) => {
95
+ const state = stateRef.current
96
+ if (state.disabled) return
97
+ event.preventDefault()
98
+ const direction = event.deltaY < 0 ? 1 : -1
99
+ const next = Math.min(
100
+ state.max,
101
+ Math.max(state.min, state.value + direction * state.step),
102
+ )
103
+ if (next !== state.value) state.onValueChange?.(next)
104
+ }
105
+
106
+ node.addEventListener('wheel', handleWheel, { passive: false })
107
+ return () => node.removeEventListener('wheel', handleWheel)
108
+ }, [])
109
+
110
+ useEffect(() => {
111
+ if (!isDragging) return undefined
112
+ const onUp = () => setIsDragging(false)
113
+ window.addEventListener('pointerup', onUp)
114
+ window.addEventListener('pointercancel', onUp)
115
+ return () => {
116
+ window.removeEventListener('pointerup', onUp)
117
+ window.removeEventListener('pointercancel', onUp)
118
+ }
119
+ }, [isDragging])
120
+
121
+ useEffect(() => {
122
+ if (!isDragging) return undefined
123
+ const style = document.createElement('style')
124
+ style.textContent = '* { cursor: ew-resize !important; }'
125
+ document.head.appendChild(style)
126
+ return () => {
127
+ if (style.parentNode) style.parentNode.removeChild(style)
128
+ }
129
+ }, [isDragging])
130
+
131
+ const setRefs = useCallback(
132
+ (node) => {
133
+ internalRef.current = node
134
+ if (typeof ref === 'function') ref(node)
135
+ else if (ref) ref.current = node
136
+ },
137
+ [ref],
138
+ )
139
+
140
+ const handleRootPointerDown = () => {
141
+ if (!disabled) setIsDragging(true)
142
+ }
143
+
144
+ const handleThumbFocus = () => {
145
+ if (lastInteractionRef.current === 'keyboard') setKeyboardFocus(true)
146
+ }
147
+ const handleThumbBlur = () => setKeyboardFocus(false)
148
+ const handleThumbPointerDown = () => setKeyboardFocus(false)
149
+ const handleThumbPointerEnter = () => setIsHovering(true)
150
+ const handleThumbPointerLeave = () => setIsHovering(false)
151
+
152
+ const displayValue = formatValue ? formatValue(value) : value
153
+ const tooltipOpen =
154
+ !disabled && (isHovering || isDragging || keyboardFocus)
155
+
156
+ const thumb = (
157
+ <SliderRadix.Thumb
158
+ className={styles.thumb}
159
+ data-focus-visible={keyboardFocus || undefined}
160
+ aria-label={ariaLabel}
161
+ onFocus={handleThumbFocus}
162
+ onBlur={handleThumbBlur}
163
+ onPointerDown={handleThumbPointerDown}
164
+ onPointerEnter={handleThumbPointerEnter}
165
+ onPointerLeave={handleThumbPointerLeave}
166
+ />
167
+ )
168
+
169
+ return (
170
+ <SliderRadix.Root
171
+ ref={setRefs}
172
+ className={classNames(styles.root, className)}
173
+ value={[value]}
174
+ onValueChange={(next) => onValueChange?.(next[0])}
175
+ min={min}
176
+ max={max}
177
+ step={step}
178
+ disabled={disabled}
179
+ onPointerDown={handleRootPointerDown}
180
+ {...props}
181
+ >
182
+ <SliderRadix.Track className={styles.track}>
183
+ <SliderRadix.Range className={styles.range} />
184
+ </SliderRadix.Track>
185
+ {showTooltip ? (
186
+ <Tooltip
187
+ content={displayValue}
188
+ open={tooltipOpen}
189
+ delayDuration={0}
190
+ side="bottom"
191
+ sideOffset={8}
192
+ updatePositionStrategy="always"
193
+ >
194
+ {thumb}
195
+ </Tooltip>
196
+ ) : (
197
+ thumb
198
+ )}
199
+ </SliderRadix.Root>
200
+ )
201
+ })
202
+
203
+ SliderLibrary.displayName = 'SliderLibrary'
@@ -0,0 +1,53 @@
1
+ .root {
2
+ position: relative;
3
+ display: flex;
4
+ align-items: center;
5
+ width: 100%;
6
+ min-width: 0;
7
+ height: 19px;
8
+ user-select: none;
9
+ touch-action: none;
10
+ }
11
+
12
+ .root[data-disabled] {
13
+ opacity: 0.5;
14
+ cursor: not-allowed;
15
+ }
16
+
17
+ .track {
18
+ position: relative;
19
+ flex-grow: 1;
20
+ height: 4px;
21
+ background: var(--neutral-alpha-3, rgba(221, 234, 248, 0.08));
22
+ border-radius: 99px;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .range {
27
+ position: absolute;
28
+ height: 100%;
29
+ background: var(--aqua-alpha-8, rgba(0, 216, 230, 0.84));
30
+ border-radius: 99px;
31
+ }
32
+
33
+ .thumb {
34
+ position: relative;
35
+ display: block;
36
+ width: 18px;
37
+ height: 10px;
38
+ background: var(--white, #fff);
39
+ border: none;
40
+ border-radius: 99px;
41
+ box-shadow: 0 0 0 1px #2e31354d;
42
+ outline: none;
43
+ cursor: ew-resize;
44
+ }
45
+
46
+ .thumb[data-focus-visible] {
47
+ outline: 2px solid var(--neutral-alpha-8, rgba(217, 237, 255, 0.36));
48
+ outline-offset: 2px;
49
+ }
50
+
51
+ .root[data-disabled] .thumb {
52
+ cursor: not-allowed;
53
+ }
@@ -0,0 +1,100 @@
1
+ import { useState } from 'react'
2
+ import { SliderLibrary } from './SliderLibrary'
3
+
4
+ export default {
5
+ title: 'Components/SliderLibrary',
6
+ component: SliderLibrary,
7
+ parameters: {
8
+ layout: 'centered',
9
+ },
10
+ argTypes: {
11
+ value: { control: { type: 'number' } },
12
+ min: { control: { type: 'number' } },
13
+ max: { control: { type: 'number' } },
14
+ step: { control: { type: 'number' } },
15
+ disabled: { control: { type: 'boolean' } },
16
+ },
17
+ decorators: [
18
+ (Story) => (
19
+ <div
20
+ style={{
21
+ width: 280,
22
+ padding: 24,
23
+ background: '#1d1d1d',
24
+ borderRadius: 8,
25
+ }}
26
+ >
27
+ <Story />
28
+ </div>
29
+ ),
30
+ ],
31
+ }
32
+
33
+ export const Default = {
34
+ render: () => {
35
+ const [value, setValue] = useState(50)
36
+ return (
37
+ <SliderLibrary
38
+ value={value}
39
+ onValueChange={setValue}
40
+ formatValue={(v) => `${v}%`}
41
+ aria-label="Volume"
42
+ />
43
+ )
44
+ },
45
+ }
46
+
47
+ export const Disabled = {
48
+ render: () => (
49
+ <SliderLibrary
50
+ value={75}
51
+ onValueChange={() => {}}
52
+ disabled
53
+ aria-label="Volume (disabled)"
54
+ />
55
+ ),
56
+ }
57
+
58
+ export const CustomRange = {
59
+ name: 'Custom range (-12 to 12 dB)',
60
+ render: () => {
61
+ const [value, setValue] = useState(0)
62
+ return (
63
+ <SliderLibrary
64
+ value={value}
65
+ onValueChange={setValue}
66
+ min={-12}
67
+ max={12}
68
+ step={0.5}
69
+ formatValue={(v) => `${v.toFixed(1)} dB`}
70
+ aria-label="Gain"
71
+ />
72
+ )
73
+ },
74
+ }
75
+
76
+ export const WithoutTooltip = {
77
+ render: () => {
78
+ const [value, setValue] = useState(50)
79
+ return (
80
+ <SliderLibrary
81
+ value={value}
82
+ onValueChange={setValue}
83
+ showTooltip={false}
84
+ aria-label="Volume"
85
+ />
86
+ )
87
+ },
88
+ }
89
+
90
+ export const WithLabel = {
91
+ render: () => {
92
+ const [value, setValue] = useState(30)
93
+ return (
94
+ <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
95
+ <span style={{ color: '#fff', fontSize: 14 }}>Volume</span>
96
+ <SliderLibrary value={value} onValueChange={setValue} />
97
+ </label>
98
+ )
99
+ },
100
+ }
@@ -0,0 +1 @@
1
+ export { SliderLibrary } from './SliderLibrary'
@@ -0,0 +1,9 @@
1
+ export const ForwardBarFillIcon = ({ width = 24, height = 24, className, ...props }) => (
2
+ <svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none" className={className} {...props}>
3
+ <path
4
+ d="M10.6187 10.6107V6.50075C10.6187 6.11585 11.0353 5.87529 11.3687 6.06774L19.2414 10.6131V6.57478C19.2414 6.29864 19.4652 6.07479 19.7414 6.07479H20.5007C20.7768 6.07479 21.0007 6.29864 21.0007 6.57479V17.0102C21.0007 17.2864 20.7768 17.5102 20.5007 17.5102H19.7414C19.4652 17.5102 19.2414 17.2864 19.2414 17.0102V12.9715L11.3687 17.5168C11.0353 17.7093 10.6187 17.4687 10.6187 17.0838V12.9738L2.75 17.5168C2.41667 17.7093 2 17.4687 2 17.0838L2 6.50075C2 6.11585 2.41667 5.87528 2.75 6.06773L10.6187 10.6107Z"
5
+ fill="currentColor" />
6
+ </svg>
7
+ )
8
+
9
+ ForwardBarFillIcon.displayName = 'ForwardBarFillIcon'
@@ -0,0 +1,7 @@
1
+ export const RewindBarFillIcon = ({ width = 24, height = 24, className, ...props }) => (
2
+ <svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none" className={className} {...props}>
3
+ <path d="M12.382 10.6107V6.50075C12.382 6.11585 11.9654 5.87529 11.632 6.06774L3.75931 10.6131V6.57478C3.75931 6.29864 3.53545 6.07479 3.25931 6.07479H2.50001C2.22387 6.07479 2.00001 6.29864 2.00001 6.57479V17.0102C2.00001 17.2864 2.22387 17.5102 2.50001 17.5102H3.25931C3.53545 17.5102 3.75931 17.2864 3.75931 17.0102V12.9715L11.632 17.5168C11.9654 17.7093 12.382 17.4687 12.382 17.0838V12.9738L20.2507 17.5168C20.584 17.7093 21.0007 17.4687 21.0007 17.0838L21.0007 6.50075C21.0007 6.11585 20.584 5.87528 20.2507 6.06773L12.382 10.6107Z" fill="currentColor" />
4
+ </svg>
5
+ )
6
+
7
+ RewindBarFillIcon.displayName = 'RewindBarFillIcon'
@@ -10,10 +10,12 @@ export const Share2Icon = ({
10
10
  viewBox="0 0 16 16"
11
11
  fill="none"
12
12
  xmlns="http://www.w3.org/2000/svg"
13
+ className={className}
14
+ {...props}
13
15
  >
14
16
  <path
15
17
  d="M10.1875 2.89583L8.00001 0.708328M8.00001 0.708328L5.81251 2.89583M8.00001 0.708328V10.1875M10.9167 5.08333C12.5275 5.08333 13.8333 6.38916 13.8333 7.99999V12.2917C13.8333 13.9485 12.4902 15.2917 10.8333 15.2917H5.16656C3.50968 15.2917 2.16653 13.9485 2.16656 12.2916L2.16663 7.99999C2.16666 6.38915 3.47251 5.08333 5.08335 5.08333"
16
- stroke="#B0B4BA"
18
+ stroke="currentColor"
17
19
  strokeLinecap="round"
18
20
  strokeLinejoin="round"
19
21
  />
package/src/icons.jsx CHANGED
@@ -433,3 +433,5 @@ export { ChartIcon } from './icons/ChartIcon'
433
433
  export { ThumbsUpIcon } from './icons/ThumbsUpIcon'
434
434
  export { ThumbsDownIcon } from './icons/ThumbsDownIcon'
435
435
  export { LightBulbIcon } from './icons/LightBulbIcon'
436
+ export { RewindBarFillIcon } from './icons/RewindBarFillIcon'
437
+ export { ForwardBarFillIcon } from './icons/ForwardBarFillIcon'
package/src/index.jsx CHANGED
@@ -1,40 +1,4 @@
1
- // Import and re-export components from Radix UI Themes
2
- import {
3
- AlertDialog,
4
- AspectRatio,
5
- Avatar,
6
- Badge,
7
- Blockquote,
8
- Box,
9
- Card,
10
- Code,
11
- Container,
12
- DataList,
13
- Dialog,
14
- Em,
15
- Flex,
16
- Grid,
17
- Heading,
18
- HoverCard,
19
- Inset,
20
- Kbd,
21
- Link,
22
- Popover,
23
- Progress,
24
- Quote,
25
- Radio,
26
- RadioGroup,
27
- ScrollArea,
28
- Section,
29
- Separator,
30
- Skeleton,
31
- Spinner,
32
- Strong,
33
- Switch,
34
- Table,
35
- Tabs,
36
- VisuallyHidden,
37
- } from '@radix-ui/themes'
1
+ // Export components from Radix UI Themes
38
2
 
39
3
  export {
40
4
  AlertDialog,
@@ -71,9 +35,10 @@ export {
71
35
  Table,
72
36
  Tabs,
73
37
  VisuallyHidden,
74
- }
38
+ } from '@radix-ui/themes'
75
39
 
76
40
  // Export our custom components
41
+
77
42
  export { AdditionalItems } from './components/AdditionalItems/AdditionalItems'
78
43
  export { BannerAnnouncement } from './components/BannerAnnouncement/BannerAnnouncement'
79
44
  export { Button } from './components/Button/Button'
@@ -115,6 +80,9 @@ export { MultiSelect } from './components/MultiSelect/MultiSelect'
115
80
  export { MultiSelectCards } from './components/MultiSelectCards/MultiSelectCards'
116
81
  export { PanControl } from './components/PanControl/PanControl'
117
82
  export { PeakLevel } from './components/PeakLevel/PeakLevel'
83
+ export { PlayerBar } from './components/PlayerBar/PlayerBar'
84
+ export { PlayerSlider } from './components/PlayerSlider/PlayerSlider'
85
+ export { PreviewCard } from './components/PreviewCard/PreviewCard'
118
86
  export { ProductsBrandPattern } from './components/ProductsBrandPattern/ProductsBrandPattern'
119
87
  export { ProductsList } from './components/ProductsList/ProductsList'
120
88
  export { ProfileMenu } from './components/ProfileMenu/ProfileMenu'
@@ -130,6 +98,8 @@ export { Shell } from './components/Shell/Shell'
130
98
  export { Sidebar } from './components/Sidebar/Sidebar'
131
99
  export { SidebarSection } from './components/Sidebar/SidebarSection/SidebarSection'
132
100
  export { Slider } from './components/Slider/Slider'
101
+ export { SliderLibrary } from './components/SliderLibrary/SliderLibrary'
102
+ export { SpecialDialog } from './components/SpecialDialog/SpecialDialog'
133
103
  export { StemGenerationForm } from './components/StemGenerationForm/StemGenerationForm'
134
104
  export { GenerationStatus } from './components/StemSeparationForm/GenerationStatus/GenerationStatus'
135
105
  export { StemSeparationForm } from './components/StemSeparationForm/StemSeparationForm'
@@ -149,9 +119,6 @@ export { TrackControlButton } from './components/TrackControlButton'
149
119
  export { TrackControlsToggle } from './components/TrackControlsToggle/TrackControlsToggle'
150
120
  export { TrackHeader } from './components/TrackHeader/TrackHeader'
151
121
  export { useForm } from './components/useForm/useForm'
122
+ export { VerticalSegmentControl } from './components/VerticalSegmentControl/VerticalSegmentControl'
152
123
  export { VoiceConversionForm } from './components/VoiceConversionForm/VoiceConversionForm'
153
124
  export { Waveform } from './components/VoiceConversionForm/Waveform/Waveform'
154
- export { PreviewCard } from './components/PreviewCard/PreviewCard'
155
- export { SpecialDialog } from './components/SpecialDialog/SpecialDialog'
156
-
157
- export { VerticalSegmentControl } from './components/VerticalSegmentControl/VerticalSegmentControl'