@moises.ai/design-system 4.16.0 → 4.16.2
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 +3575 -3465
- package/package.json +1 -1
- package/src/components/InstrumentSelect/InstrumentSelect.jsx +3 -3
- package/src/components/InstrumentSelect/InstrumentSelect.module.css +4 -3
- package/src/components/SliderLibrary/SliderLibrary.jsx +203 -0
- package/src/components/SliderLibrary/SliderLibrary.module.css +53 -0
- package/src/components/SliderLibrary/SliderLibrary.stories.jsx +100 -0
- package/src/components/SliderLibrary/index.js +1 -0
- package/src/index.jsx +1 -0
package/package.json
CHANGED
|
@@ -58,8 +58,8 @@ function isItemSelected(value, itemValue) {
|
|
|
58
58
|
* - Pass `value` to mark items as selected (string for single, string[] for multi).
|
|
59
59
|
* - `onSelect(item)` fires for every click; the parent decides what to do.
|
|
60
60
|
*
|
|
61
|
-
* Layout: 2-column responsive grid by default with 4px gap. Cards have
|
|
62
|
-
* and
|
|
61
|
+
* Layout: 2-column responsive grid by default with 4px gap. Cards have no minimum width
|
|
62
|
+
* and shrink to fit narrow containers (the parent owns the available width).
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
65
65
|
* // Single select
|
|
@@ -115,7 +115,7 @@ export const InstrumentSelect = forwardRef(function InstrumentSelect(
|
|
|
115
115
|
}, [columns])
|
|
116
116
|
|
|
117
117
|
const gridStyle = useMemo(
|
|
118
|
-
() => ({ gridTemplateColumns: `repeat(${cols}, minmax(
|
|
118
|
+
() => ({ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }),
|
|
119
119
|
[cols],
|
|
120
120
|
)
|
|
121
121
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.title {
|
|
17
|
+
font-family: var(--default-font-family, 'Articulat', helvetica, arial, sans-serif);
|
|
17
18
|
color: var(--neutral-alpha-12);
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
|
|
35
36
|
.grid {
|
|
36
37
|
display: grid;
|
|
37
|
-
grid-template-columns: repeat(2, minmax(
|
|
38
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
38
39
|
gap: 4px;
|
|
39
40
|
width: 100%;
|
|
40
41
|
}
|
|
@@ -46,13 +47,13 @@
|
|
|
46
47
|
display: flex;
|
|
47
48
|
align-items: center;
|
|
48
49
|
gap: var(--space-3, 12px);
|
|
49
|
-
min-width:
|
|
50
|
+
min-width: 0;
|
|
50
51
|
padding: 10px 8px 10px 12px;
|
|
51
52
|
border: 1px solid var(--neutral-alpha-4, rgba(211, 237, 248, 0.11));
|
|
52
53
|
border-radius: var(--radius-3, 6px);
|
|
53
54
|
background: transparent;
|
|
54
55
|
color: var(--neutral-alpha-12);
|
|
55
|
-
font-family:
|
|
56
|
+
font-family: var(--default-font-family, 'Articulat', helvetica, arial, sans-serif);
|
|
56
57
|
font-size: 14px;
|
|
57
58
|
line-height: 20px;
|
|
58
59
|
text-align: left;
|
|
@@ -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'
|
package/src/index.jsx
CHANGED
|
@@ -130,6 +130,7 @@ export { Shell } from './components/Shell/Shell'
|
|
|
130
130
|
export { Sidebar } from './components/Sidebar/Sidebar'
|
|
131
131
|
export { SidebarSection } from './components/Sidebar/SidebarSection/SidebarSection'
|
|
132
132
|
export { Slider } from './components/Slider/Slider'
|
|
133
|
+
export { SliderLibrary } from './components/SliderLibrary/SliderLibrary'
|
|
133
134
|
export { StemGenerationForm } from './components/StemGenerationForm/StemGenerationForm'
|
|
134
135
|
export { GenerationStatus } from './components/StemSeparationForm/GenerationStatus/GenerationStatus'
|
|
135
136
|
export { StemSeparationForm } from './components/StemSeparationForm/StemSeparationForm'
|