@moises.ai/design-system 4.17.4 → 4.17.5
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 +3444 -3360
- package/package.json +1 -1
- package/src/components/SliderLibrary/SliderLibrary.jsx +7 -0
- package/src/components/SliderLibrary/SliderLibrary.stories.jsx +22 -0
- package/src/components/ToolsPopover/ToolsPopover.jsx +114 -0
- package/src/components/ToolsPopover/ToolsPopover.module.css +88 -0
- package/src/components/ToolsPopover/ToolsPopover.stories.jsx +139 -0
- package/src/index.jsx +1 -0
package/package.json
CHANGED
|
@@ -17,6 +17,8 @@ import styles from './SliderLibrary.module.css'
|
|
|
17
17
|
* hover/focus/drag with the current value.
|
|
18
18
|
* @property {(value: number) => React.ReactNode} [formatValue] - Formats the value for
|
|
19
19
|
* display in the tooltip. Defaults to the raw number.
|
|
20
|
+
* @property {(value: number) => void} [onThumbDoubleClick] - Fires when the thumb is
|
|
21
|
+
* double-clicked. Receives the current value.
|
|
20
22
|
* @property {string} [aria-label] - Accessible label, recommended when no visual label exists.
|
|
21
23
|
* @property {string} [className] - Extra class on the root.
|
|
22
24
|
*/
|
|
@@ -57,6 +59,7 @@ export const SliderLibrary = forwardRef(function SliderLibrary(
|
|
|
57
59
|
disabled = false,
|
|
58
60
|
showTooltip = true,
|
|
59
61
|
formatValue,
|
|
62
|
+
onThumbDoubleClick,
|
|
60
63
|
className,
|
|
61
64
|
...props
|
|
62
65
|
},
|
|
@@ -148,6 +151,9 @@ export const SliderLibrary = forwardRef(function SliderLibrary(
|
|
|
148
151
|
const handleThumbPointerDown = () => setKeyboardFocus(false)
|
|
149
152
|
const handleThumbPointerEnter = () => setIsHovering(true)
|
|
150
153
|
const handleThumbPointerLeave = () => setIsHovering(false)
|
|
154
|
+
const handleThumbDoubleClick = () => {
|
|
155
|
+
if (!disabled) onThumbDoubleClick?.(value)
|
|
156
|
+
}
|
|
151
157
|
|
|
152
158
|
const displayValue = formatValue ? formatValue(value) : value
|
|
153
159
|
const tooltipOpen =
|
|
@@ -163,6 +169,7 @@ export const SliderLibrary = forwardRef(function SliderLibrary(
|
|
|
163
169
|
onPointerDown={handleThumbPointerDown}
|
|
164
170
|
onPointerEnter={handleThumbPointerEnter}
|
|
165
171
|
onPointerLeave={handleThumbPointerLeave}
|
|
172
|
+
onDoubleClick={onThumbDoubleClick ? handleThumbDoubleClick : undefined}
|
|
166
173
|
/>
|
|
167
174
|
)
|
|
168
175
|
|
|
@@ -98,3 +98,25 @@ export const WithLabel = {
|
|
|
98
98
|
)
|
|
99
99
|
},
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
export const ThumbDoubleClick = {
|
|
103
|
+
name: 'Thumb double-click',
|
|
104
|
+
render: () => {
|
|
105
|
+
const defaultValue = 0
|
|
106
|
+
const [value, setValue] = useState(35)
|
|
107
|
+
return (
|
|
108
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
109
|
+
<SliderLibrary
|
|
110
|
+
value={value}
|
|
111
|
+
onValueChange={setValue}
|
|
112
|
+
onThumbDoubleClick={() => setValue(defaultValue)}
|
|
113
|
+
formatValue={(v) => `${v}%`}
|
|
114
|
+
aria-label="Volume"
|
|
115
|
+
/>
|
|
116
|
+
<span style={{ color: '#a1a1a1', fontSize: 12 }}>
|
|
117
|
+
Double-click the thumb to reset to {defaultValue}%
|
|
118
|
+
</span>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
},
|
|
122
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import { Popover as PopoverRadix } from 'radix-ui'
|
|
3
|
+
import { memo, useCallback, useState } from 'react'
|
|
4
|
+
import { ScrollArea } from '../../index'
|
|
5
|
+
import { Theme } from '../theme/Theme'
|
|
6
|
+
import styles from './ToolsPopover.module.css'
|
|
7
|
+
|
|
8
|
+
export const ToolsPopover = memo(
|
|
9
|
+
({
|
|
10
|
+
trigger,
|
|
11
|
+
children,
|
|
12
|
+
className,
|
|
13
|
+
contentClassName,
|
|
14
|
+
triggerClassName,
|
|
15
|
+
disabled = false,
|
|
16
|
+
maxHeight,
|
|
17
|
+
sideOffset = 5,
|
|
18
|
+
matchTriggerWidth = false,
|
|
19
|
+
wrapCloseMenu,
|
|
20
|
+
modal = false,
|
|
21
|
+
...props
|
|
22
|
+
}) => {
|
|
23
|
+
const isControlled = props.open !== undefined
|
|
24
|
+
const [internalOpen, setInternalOpen] = useState(false)
|
|
25
|
+
|
|
26
|
+
const open = isControlled ? props.open : internalOpen
|
|
27
|
+
const onOpenChange = useCallback(
|
|
28
|
+
(value) => {
|
|
29
|
+
if (!isControlled) setInternalOpen(value)
|
|
30
|
+
props?.onOpenChange?.(value)
|
|
31
|
+
},
|
|
32
|
+
[isControlled, props?.onOpenChange],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const baseClose = useCallback(() => onOpenChange(false), [onOpenChange])
|
|
36
|
+
const closePopover = wrapCloseMenu ? wrapCloseMenu(baseClose) : baseClose
|
|
37
|
+
|
|
38
|
+
const contentStyle = {
|
|
39
|
+
...(matchTriggerWidth && {
|
|
40
|
+
width: 'var(--radix-popover-trigger-width)',
|
|
41
|
+
boxSizing: 'border-box',
|
|
42
|
+
}),
|
|
43
|
+
...(maxHeight && { height: maxHeight }),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const handlePanelKeyDown = useCallback((event) => {
|
|
47
|
+
if (event.key === 'Tab') {
|
|
48
|
+
event.stopPropagation()
|
|
49
|
+
}
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
52
|
+
const handleOpenAutoFocus = useCallback((event) => {
|
|
53
|
+
event.preventDefault()
|
|
54
|
+
const panel = event.currentTarget.querySelector('[data-tools-popover-panel]')
|
|
55
|
+
panel?.focus({ preventScroll: true })
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
const renderPanel = () => (
|
|
59
|
+
<div
|
|
60
|
+
className={styles.contentPanel}
|
|
61
|
+
data-tools-popover-panel
|
|
62
|
+
tabIndex={-1}
|
|
63
|
+
onKeyDown={handlePanelKeyDown}
|
|
64
|
+
>
|
|
65
|
+
{typeof children === 'function' ? children(closePopover) : children}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const popoverContent = (
|
|
70
|
+
<Theme asChild>
|
|
71
|
+
<PopoverRadix.Content
|
|
72
|
+
className={classNames(styles.popoverContent, className, contentClassName)}
|
|
73
|
+
side={props.side}
|
|
74
|
+
align={props.align}
|
|
75
|
+
style={contentStyle}
|
|
76
|
+
sideOffset={sideOffset}
|
|
77
|
+
alignOffset={0}
|
|
78
|
+
onOpenAutoFocus={handleOpenAutoFocus}
|
|
79
|
+
onCloseAutoFocus={(event) => {
|
|
80
|
+
event.preventDefault()
|
|
81
|
+
}}
|
|
82
|
+
onClick={(event) => event.stopPropagation()}
|
|
83
|
+
>
|
|
84
|
+
{maxHeight ? (
|
|
85
|
+
<ScrollArea scrollbars="vertical" style={{ height: maxHeight }}>
|
|
86
|
+
{renderPanel()}
|
|
87
|
+
</ScrollArea>
|
|
88
|
+
) : (
|
|
89
|
+
renderPanel()
|
|
90
|
+
)}
|
|
91
|
+
</PopoverRadix.Content>
|
|
92
|
+
</Theme>
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<PopoverRadix.Root
|
|
97
|
+
{...props}
|
|
98
|
+
modal={modal}
|
|
99
|
+
open={disabled ? false : open}
|
|
100
|
+
onOpenChange={disabled ? undefined : onOpenChange}
|
|
101
|
+
>
|
|
102
|
+
<PopoverRadix.Trigger asChild className={triggerClassName} disabled={disabled}>
|
|
103
|
+
{trigger}
|
|
104
|
+
</PopoverRadix.Trigger>
|
|
105
|
+
|
|
106
|
+
<PopoverRadix.Portal className={styles.popoverPortal}>
|
|
107
|
+
{popoverContent}
|
|
108
|
+
</PopoverRadix.Portal>
|
|
109
|
+
</PopoverRadix.Root>
|
|
110
|
+
)
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
ToolsPopover.displayName = 'ToolsPopover'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.popoverPortal {
|
|
2
|
+
border-radius: 8px;
|
|
3
|
+
border: 1px solid var(--neutral-alpha-3);
|
|
4
|
+
background: #18191b;
|
|
5
|
+
box-shadow: 0 12px 32px -16px rgba(0, 0, 0, 0.3),
|
|
6
|
+
0 12px 60px 0 rgba(0, 0, 0, 0.15);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.popoverContent {
|
|
10
|
+
border-radius: 8px;
|
|
11
|
+
width: fit-content;
|
|
12
|
+
background-color: var(--panel-solid);
|
|
13
|
+
padding: 12px 16px 16px 16px;
|
|
14
|
+
box-shadow: 0 12px 32px -16px rgba(0, 0, 0, 0.3),
|
|
15
|
+
0 12px 60px 0 rgba(0, 0, 0, 0.15);
|
|
16
|
+
border: 1px solid var(--neutral-alpha-3);
|
|
17
|
+
animation-duration: 400ms;
|
|
18
|
+
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
|
19
|
+
will-change: transform, opacity;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.popoverContent[data-side='top'] {
|
|
23
|
+
animation-name: slideDownAndFade;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.popoverContent[data-side='right'] {
|
|
27
|
+
animation-name: slideLeftAndFade;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.popoverContent[data-side='bottom'] {
|
|
31
|
+
animation-name: slideUpAndFade;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.popoverContent[data-side='left'] {
|
|
35
|
+
animation-name: slideRightAndFade;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@keyframes slideUpAndFade {
|
|
39
|
+
from {
|
|
40
|
+
opacity: 0;
|
|
41
|
+
transform: translateY(2px);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
to {
|
|
45
|
+
opacity: 1;
|
|
46
|
+
transform: translateY(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@keyframes slideRightAndFade {
|
|
51
|
+
from {
|
|
52
|
+
opacity: 0;
|
|
53
|
+
transform: translateX(-2px);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
to {
|
|
57
|
+
opacity: 1;
|
|
58
|
+
transform: translateX(0);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes slideDownAndFade {
|
|
63
|
+
from {
|
|
64
|
+
opacity: 0;
|
|
65
|
+
transform: translateY(-2px);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
to {
|
|
69
|
+
opacity: 1;
|
|
70
|
+
transform: translateY(0);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes slideLeftAndFade {
|
|
75
|
+
from {
|
|
76
|
+
opacity: 0;
|
|
77
|
+
transform: translateX(2px);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
to {
|
|
81
|
+
opacity: 1;
|
|
82
|
+
transform: translateX(0);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.contentPanel {
|
|
87
|
+
outline: none;
|
|
88
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Flex, Switch, Text } from '@radix-ui/themes'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { MetronomeIcon } from '../../icons/MetronomeIcon'
|
|
4
|
+
import { Button } from '../Button/Button'
|
|
5
|
+
import { IconButton } from '../IconButton/IconButton'
|
|
6
|
+
import { ToolsPopover } from './ToolsPopover'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title: 'Components/ToolsPopover',
|
|
10
|
+
component: ToolsPopover,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'centered',
|
|
13
|
+
docs: {
|
|
14
|
+
description: {
|
|
15
|
+
component: `
|
|
16
|
+
ToolsPopover is a Popover wrapper with controlled open state, styled panel content, and focus management.
|
|
17
|
+
|
|
18
|
+
Pass any single React element as \`trigger\` — it is forwarded to \`Popover.Trigger\` via \`asChild\`.
|
|
19
|
+
|
|
20
|
+
Use this instead of \`ToolsDropdown\` when the panel contains interactive content (forms, switches, sliders) that should stay open while the user interacts with it.
|
|
21
|
+
`,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
tags: ['autodocs'],
|
|
26
|
+
argTypes: {
|
|
27
|
+
trigger: {
|
|
28
|
+
description: 'Any single React element used as the popover trigger',
|
|
29
|
+
table: { type: { summary: 'React.ReactElement' } },
|
|
30
|
+
},
|
|
31
|
+
modal: {
|
|
32
|
+
control: 'boolean',
|
|
33
|
+
table: { type: { summary: 'boolean' }, defaultValue: { summary: false } },
|
|
34
|
+
},
|
|
35
|
+
children: {
|
|
36
|
+
description: 'Popover content or render prop receiving closePopover',
|
|
37
|
+
table: { type: { summary: 'React.ReactNode | (closePopover) => React.ReactNode' } },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const Default = {
|
|
43
|
+
render: () => (
|
|
44
|
+
<ToolsPopover trigger={<Button variant="soft">Settings</Button>}>
|
|
45
|
+
<Text size="2">Tool settings panel</Text>
|
|
46
|
+
</ToolsPopover>
|
|
47
|
+
),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const WithIconButton = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<ToolsPopover trigger={<IconButton><MetronomeIcon width={16} height={16} /></IconButton>}>
|
|
53
|
+
<Flex direction="column" gap="2">
|
|
54
|
+
<Text size="2">BPM: 120</Text>
|
|
55
|
+
<Text size="2">Time signature: 4/4</Text>
|
|
56
|
+
</Flex>
|
|
57
|
+
</ToolsPopover>
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const WithInteractiveContent = {
|
|
62
|
+
render: () => {
|
|
63
|
+
const [enabled, setEnabled] = useState(false)
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<ToolsPopover trigger={<Button variant="ghost">Chords</Button>}>
|
|
67
|
+
{(closePopover) => (
|
|
68
|
+
<Flex direction="column" gap="3" width="220px">
|
|
69
|
+
<Text size="2">Show chord diagrams</Text>
|
|
70
|
+
<Switch size="2" checked={enabled} onCheckedChange={setEnabled} />
|
|
71
|
+
<Button
|
|
72
|
+
variant="soft"
|
|
73
|
+
onClick={() => {
|
|
74
|
+
closePopover()
|
|
75
|
+
alert('Saved')
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
Save
|
|
79
|
+
</Button>
|
|
80
|
+
</Flex>
|
|
81
|
+
)}
|
|
82
|
+
</ToolsPopover>
|
|
83
|
+
)
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const ControlledOpen = {
|
|
88
|
+
render: () => {
|
|
89
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<Flex direction="column" gap="4" align="center">
|
|
93
|
+
<Flex gap="2">
|
|
94
|
+
<button type="button" onClick={() => setIsOpen(true)}>
|
|
95
|
+
Open
|
|
96
|
+
</button>
|
|
97
|
+
<button type="button" onClick={() => setIsOpen(false)}>
|
|
98
|
+
Close
|
|
99
|
+
</button>
|
|
100
|
+
</Flex>
|
|
101
|
+
|
|
102
|
+
<ToolsPopover
|
|
103
|
+
open={isOpen}
|
|
104
|
+
onOpenChange={setIsOpen}
|
|
105
|
+
trigger={<Button variant="outline">Controlled</Button>}
|
|
106
|
+
>
|
|
107
|
+
<Text size="2">Controlled popover content</Text>
|
|
108
|
+
</ToolsPopover>
|
|
109
|
+
|
|
110
|
+
<Text size="1">
|
|
111
|
+
Status: <strong>{isOpen ? 'Open' : 'Closed'}</strong>
|
|
112
|
+
</Text>
|
|
113
|
+
</Flex>
|
|
114
|
+
)
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const CustomElement = {
|
|
119
|
+
render: () => (
|
|
120
|
+
<ToolsPopover
|
|
121
|
+
trigger={
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
style={{
|
|
125
|
+
padding: '8px 12px',
|
|
126
|
+
borderRadius: 4,
|
|
127
|
+
border: '1px solid var(--neutral-alpha-6)',
|
|
128
|
+
background: 'transparent',
|
|
129
|
+
cursor: 'pointer',
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
Custom trigger
|
|
133
|
+
</button>
|
|
134
|
+
}
|
|
135
|
+
>
|
|
136
|
+
<Text size="2">Any element can be the trigger</Text>
|
|
137
|
+
</ToolsPopover>
|
|
138
|
+
),
|
|
139
|
+
}
|
package/src/index.jsx
CHANGED
|
@@ -55,6 +55,7 @@ export { DataTable } from './components/DataTable/DataTable'
|
|
|
55
55
|
export { DropdownButton } from './components/DropdownButton/DropdownButton'
|
|
56
56
|
export { DropdownMenu } from './components/DropdownMenu/DropdownMenu'
|
|
57
57
|
export { ToolsDropdown } from './components/ToolsDropdown/ToolsDropdown'
|
|
58
|
+
export { ToolsPopover } from './components/ToolsPopover/ToolsPopover'
|
|
58
59
|
export { EmptyState } from './components/EmptyState/EmptyState'
|
|
59
60
|
export {
|
|
60
61
|
Extension,
|