@dxos/react-ui-pickers 0.7.5-labs.c0e040f → 0.7.5-labs.d199c0f
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/lib/browser/index.mjs +137 -97
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +144 -109
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +137 -97
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/EmojiPicker.d.ts +3 -3
- package/dist/types/src/components/EmojiPicker.d.ts.map +1 -1
- package/dist/types/src/components/EmojiPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/HuePicker.d.ts +7 -14
- package/dist/types/src/components/HuePicker.d.ts.map +1 -1
- package/dist/types/src/components/HuePicker.stories.d.ts +1 -2
- package/dist/types/src/components/HuePicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/IconPicker.d.ts +11 -0
- package/dist/types/src/components/IconPicker.d.ts.map +1 -0
- package/dist/types/src/components/IconPicker.stories.d.ts +7 -0
- package/dist/types/src/components/IconPicker.stories.d.ts.map +1 -0
- package/dist/types/src/components/ToolbarPicker.d.ts +17 -0
- package/dist/types/src/components/ToolbarPicker.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/components/EmojiPicker.stories.tsx +4 -1
- package/src/components/EmojiPicker.tsx +1 -0
- package/src/components/HuePicker.stories.tsx +14 -29
- package/src/components/HuePicker.tsx +24 -172
- package/src/components/IconPicker.stories.tsx +39 -0
- package/src/components/IconPicker.tsx +92 -0
- package/src/components/ToolbarPicker.tsx +112 -0
- package/src/components/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-pickers",
|
|
3
|
-
"version": "0.7.5-labs.
|
|
3
|
+
"version": "0.7.5-labs.d199c0f",
|
|
4
4
|
"description": "A collection of picker components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "DXOS.org",
|
|
9
|
+
"type": "module",
|
|
9
10
|
"exports": {
|
|
10
11
|
".": {
|
|
11
12
|
"types": "./dist/types/src/index.d.ts",
|
|
@@ -25,8 +26,8 @@
|
|
|
25
26
|
"@emoji-mart/data": "^1.1.2",
|
|
26
27
|
"@emoji-mart/react": "^1.1.1",
|
|
27
28
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
28
|
-
"@dxos/
|
|
29
|
-
"@dxos/
|
|
29
|
+
"@dxos/react-ui-types": "0.7.5-labs.d199c0f",
|
|
30
|
+
"@dxos/util": "0.7.5-labs.d199c0f"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@types/react": "~18.2.0",
|
|
@@ -34,15 +35,15 @@
|
|
|
34
35
|
"react": "~18.2.0",
|
|
35
36
|
"react-dom": "~18.2.0",
|
|
36
37
|
"vite": "5.4.7",
|
|
37
|
-
"@dxos/react-ui": "0.7.5-labs.
|
|
38
|
-
"@dxos/react-ui
|
|
39
|
-
"@dxos/storybook-utils": "0.7.5-labs.
|
|
38
|
+
"@dxos/react-ui-theme": "0.7.5-labs.d199c0f",
|
|
39
|
+
"@dxos/react-ui": "0.7.5-labs.d199c0f",
|
|
40
|
+
"@dxos/storybook-utils": "0.7.5-labs.d199c0f"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
43
|
"react": "~18.2.0",
|
|
43
44
|
"react-dom": "~18.2.0",
|
|
44
|
-
"@dxos/react-ui": "0.7.5-labs.
|
|
45
|
-
"@dxos/react-ui-theme": "0.7.5-labs.
|
|
45
|
+
"@dxos/react-ui": "0.7.5-labs.d199c0f",
|
|
46
|
+
"@dxos/react-ui-theme": "0.7.5-labs.d199c0f"
|
|
46
47
|
},
|
|
47
48
|
"publishConfig": {
|
|
48
49
|
"access": "public"
|
|
@@ -53,7 +53,10 @@ export const Block: StoryObj<EmojiPickerProps> = {
|
|
|
53
53
|
|
|
54
54
|
const meta: Meta = {
|
|
55
55
|
title: 'ui/react-ui-pickers/EmojiPicker',
|
|
56
|
-
decorators: [withTheme, withLayout({
|
|
56
|
+
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
57
|
+
parameters: {
|
|
58
|
+
layout: 'centered',
|
|
59
|
+
},
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
export default meta;
|
|
@@ -121,6 +121,7 @@ export const EmojiPickerToolbarButton = ({
|
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* A button for picking an emoji alongside a button for unsetting it.
|
|
124
|
+
* @deprecated
|
|
124
125
|
*/
|
|
125
126
|
export const EmojiPickerBlock = ({ disabled, defaultEmoji, emoji, onChangeEmoji, onClickClear }: EmojiPickerProps) => {
|
|
126
127
|
const { t } = useTranslation('os');
|
|
@@ -10,46 +10,31 @@ import React, { useState } from 'react';
|
|
|
10
10
|
import { Toolbar } from '@dxos/react-ui';
|
|
11
11
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { HuePicker, type HuePickerProps } from './HuePicker';
|
|
14
14
|
|
|
15
15
|
const ToolbarStory = (props: HuePickerProps) => {
|
|
16
|
-
const [hue, setHue] = useState<string>(props.
|
|
16
|
+
const [hue, setHue] = useState<string | undefined>(props.defaultValue);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<Toolbar.Root>
|
|
20
|
-
<
|
|
20
|
+
<HuePicker {...props} value={hue} onChange={setHue} onReset={() => setHue(undefined)} />
|
|
21
21
|
</Toolbar.Root>
|
|
22
22
|
);
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
const BlockStory = (props: HuePickerProps) => {
|
|
26
|
-
const [hue, setHue] = useState<string>(props.defaultHue ?? 'red');
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className='flex gap-2'>
|
|
30
|
-
<HuePickerBlock
|
|
31
|
-
{...props}
|
|
32
|
-
hue={hue}
|
|
33
|
-
onChangeHue={setHue}
|
|
34
|
-
onClickClear={() => setHue(props.defaultHue ?? 'red')}
|
|
35
|
-
/>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const ToolbarButtonStory: StoryObj<HuePickerProps> = {
|
|
41
|
-
render: ToolbarStory,
|
|
42
|
-
args: { defaultHue: 'red' },
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const BlockPickerStory: StoryObj<HuePickerProps> = {
|
|
46
|
-
render: BlockStory,
|
|
47
|
-
args: { defaultHue: 'red' },
|
|
48
|
-
};
|
|
49
|
-
|
|
50
25
|
const meta: Meta = {
|
|
51
26
|
title: 'ui/react-ui-pickers/HuePicker',
|
|
52
|
-
decorators: [withTheme, withLayout({
|
|
27
|
+
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
28
|
+
parameters: {
|
|
29
|
+
layout: 'centered',
|
|
30
|
+
},
|
|
53
31
|
};
|
|
54
32
|
|
|
55
33
|
export default meta;
|
|
34
|
+
|
|
35
|
+
export const Default: StoryObj<HuePickerProps> = {
|
|
36
|
+
render: ToolbarStory,
|
|
37
|
+
args: {
|
|
38
|
+
defaultValue: 'red',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -2,190 +2,42 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import React, { useRef, useState } from 'react';
|
|
5
|
+
import React from 'react';
|
|
7
6
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
type ButtonProps,
|
|
11
|
-
DropdownMenu,
|
|
12
|
-
Icon,
|
|
13
|
-
type ThemedClassName,
|
|
14
|
-
Toolbar,
|
|
15
|
-
Tooltip,
|
|
16
|
-
useTranslation,
|
|
17
|
-
} from '@dxos/react-ui';
|
|
18
|
-
import { hues, mx } from '@dxos/react-ui-theme';
|
|
7
|
+
import { type ButtonProps, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
import { hues } from '@dxos/react-ui-theme';
|
|
19
9
|
|
|
20
|
-
|
|
21
|
-
const size = 20;
|
|
22
|
-
return (
|
|
23
|
-
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
24
|
-
<rect x={0} y={0} width={size} height={size} fill={`var(--dx-${hue}Surface)`} />
|
|
25
|
-
<text x='10' y='15' textAnchor='middle' fontSize='14' fontWeight='bold' fill={`var(--dx-${hue}SurfaceText)`}>
|
|
26
|
-
T
|
|
27
|
-
</text>
|
|
28
|
-
</svg>
|
|
29
|
-
);
|
|
30
|
-
};
|
|
10
|
+
import { ToolbarPickerButton, type ToolbarPickerProps } from './ToolbarPicker';
|
|
31
11
|
|
|
32
12
|
export type HuePickerProps = {
|
|
33
13
|
disabled?: boolean;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
value?: string;
|
|
16
|
+
onChange?: (nextHue: string) => void;
|
|
17
|
+
onReset?: ButtonProps['onClick'];
|
|
18
|
+
} & Pick<ToolbarPickerProps, 'disabled' | 'defaultValue' | 'value' | 'onChange' | 'onReset'>;
|
|
39
19
|
|
|
40
|
-
|
|
41
|
-
* A toolbar button for picking hue. Use only in `role=toolbar` elements. Unable to unset the value.
|
|
42
|
-
*/
|
|
43
|
-
export const HuePickerToolbarButton = ({
|
|
44
|
-
disabled,
|
|
45
|
-
hue,
|
|
46
|
-
onChangeHue,
|
|
47
|
-
classNames,
|
|
48
|
-
defaultHue,
|
|
49
|
-
}: ThemedClassName<Omit<HuePickerProps, 'onClickClear'>>) => {
|
|
20
|
+
export const HuePicker = (props: ThemedClassName<HuePickerProps>) => {
|
|
50
21
|
const { t } = useTranslation('os');
|
|
51
22
|
|
|
52
|
-
const [hueValue, setHueValue] = useControllableState<string>({
|
|
53
|
-
prop: hue,
|
|
54
|
-
onChange: onChangeHue,
|
|
55
|
-
defaultProp: defaultHue,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const [huePickerOpen, setHuePickerOpen] = useState<boolean>(false);
|
|
59
|
-
|
|
60
|
-
const suppressNextTooltip = useRef<boolean>(false);
|
|
61
|
-
const [triggerTooltipOpen, setTriggerTooltipOpen] = useState(false);
|
|
62
|
-
|
|
63
23
|
return (
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
setTriggerTooltipOpen(nextOpen);
|
|
72
|
-
}
|
|
73
|
-
}}
|
|
74
|
-
>
|
|
75
|
-
<DropdownMenu.Root
|
|
76
|
-
modal={false}
|
|
77
|
-
open={huePickerOpen}
|
|
78
|
-
onOpenChange={(nextOpen) => {
|
|
79
|
-
setHuePickerOpen(nextOpen);
|
|
80
|
-
suppressNextTooltip.current = true;
|
|
81
|
-
}}
|
|
82
|
-
>
|
|
83
|
-
<Tooltip.Trigger asChild>
|
|
84
|
-
<DropdownMenu.Trigger asChild>
|
|
85
|
-
<Toolbar.Button classNames={mx('gap-2 plb-1', classNames)} disabled={disabled}>
|
|
86
|
-
<span className='sr-only'>{t('select hue label')}</span>
|
|
87
|
-
<Icon icon='ph--palette--regular' size={5} />
|
|
88
|
-
</Toolbar.Button>
|
|
89
|
-
</DropdownMenu.Trigger>
|
|
90
|
-
</Tooltip.Trigger>
|
|
91
|
-
<Tooltip.Portal>
|
|
92
|
-
<Tooltip.Content side='bottom'>
|
|
93
|
-
{t('select hue label')}
|
|
94
|
-
<Tooltip.Arrow />
|
|
95
|
-
</Tooltip.Content>
|
|
96
|
-
</Tooltip.Portal>
|
|
97
|
-
<DropdownMenu.Portal>
|
|
98
|
-
<DropdownMenu.Content side='bottom' classNames='!w-40'>
|
|
99
|
-
<DropdownMenu.Viewport classNames='grid grid-cols-6'>
|
|
100
|
-
{hues.map((hue) => {
|
|
101
|
-
return (
|
|
102
|
-
<DropdownMenu.CheckboxItem
|
|
103
|
-
key={hue}
|
|
104
|
-
checked={hue === hueValue}
|
|
105
|
-
onCheckedChange={() => setHueValue(hue)}
|
|
106
|
-
classNames={'px-0 py-2 items-center justify-center'}
|
|
107
|
-
>
|
|
108
|
-
<HuePreview hue={hue} />
|
|
109
|
-
</DropdownMenu.CheckboxItem>
|
|
110
|
-
);
|
|
111
|
-
})}
|
|
112
|
-
</DropdownMenu.Viewport>
|
|
113
|
-
<DropdownMenu.Arrow />
|
|
114
|
-
</DropdownMenu.Content>
|
|
115
|
-
</DropdownMenu.Portal>
|
|
116
|
-
</DropdownMenu.Root>
|
|
117
|
-
</Tooltip.Root>
|
|
24
|
+
<ToolbarPickerButton
|
|
25
|
+
Component={HuePreview}
|
|
26
|
+
label={t('select hue label')}
|
|
27
|
+
icon='ph--palette--regular'
|
|
28
|
+
values={hues}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
118
31
|
);
|
|
119
32
|
};
|
|
120
33
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*/
|
|
124
|
-
export const HuePickerBlock = ({ disabled, hue, onChangeHue, defaultHue, onClickClear }: HuePickerProps) => {
|
|
125
|
-
const { t } = useTranslation('os');
|
|
126
|
-
|
|
127
|
-
const [hueValue, setHueValue] = useControllableState<string>({
|
|
128
|
-
prop: hue,
|
|
129
|
-
onChange: onChangeHue,
|
|
130
|
-
defaultProp: defaultHue,
|
|
131
|
-
});
|
|
132
|
-
|
|
34
|
+
const HuePreview = ({ value }: { value: string }) => {
|
|
35
|
+
const size = 16;
|
|
133
36
|
return (
|
|
134
|
-
|
|
135
|
-
<
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<div role='none' className='pis-14 grow flex items-center justify-center gap-2'>
|
|
140
|
-
{hue ? (
|
|
141
|
-
<>
|
|
142
|
-
<HuePreview hue={hueValue!} />
|
|
143
|
-
<span>{t(`${hueValue} label`)}</span>
|
|
144
|
-
</>
|
|
145
|
-
) : (
|
|
146
|
-
<span>{t('select a hue label')}</span>
|
|
147
|
-
)}
|
|
148
|
-
</div>
|
|
149
|
-
<Icon icon='ph--caret-down--regular' size={4} />
|
|
150
|
-
</Button>
|
|
151
|
-
</DropdownMenu.Trigger>
|
|
152
|
-
<DropdownMenu.Portal>
|
|
153
|
-
<DropdownMenu.Content side='right'>
|
|
154
|
-
<DropdownMenu.Viewport>
|
|
155
|
-
{hues.map((hue) => {
|
|
156
|
-
return (
|
|
157
|
-
<DropdownMenu.CheckboxItem
|
|
158
|
-
key={hue}
|
|
159
|
-
checked={hue === hueValue}
|
|
160
|
-
onCheckedChange={() => setHueValue(hue)}
|
|
161
|
-
>
|
|
162
|
-
<HuePreview hue={hue} />
|
|
163
|
-
<span className='grow'>{t(`${hue} label`)}</span>
|
|
164
|
-
<DropdownMenu.ItemIndicator>
|
|
165
|
-
<Icon icon='ph--check--regular' size={4} />
|
|
166
|
-
</DropdownMenu.ItemIndicator>
|
|
167
|
-
</DropdownMenu.CheckboxItem>
|
|
168
|
-
);
|
|
169
|
-
})}
|
|
170
|
-
</DropdownMenu.Viewport>
|
|
171
|
-
<DropdownMenu.Arrow />
|
|
172
|
-
</DropdownMenu.Content>
|
|
173
|
-
</DropdownMenu.Portal>
|
|
174
|
-
</DropdownMenu.Root>
|
|
175
|
-
<Tooltip.Root>
|
|
176
|
-
<Tooltip.Trigger asChild>
|
|
177
|
-
<Button variant='ghost' onClick={onClickClear} disabled={disabled}>
|
|
178
|
-
<span className='sr-only'>{t('clear label')}</span>
|
|
179
|
-
<Icon icon='ph--arrow-counter-clockwise--regular' size={5} />
|
|
180
|
-
</Button>
|
|
181
|
-
</Tooltip.Trigger>
|
|
182
|
-
<Tooltip.Portal>
|
|
183
|
-
<Tooltip.Content side='right'>
|
|
184
|
-
{t('clear label')}
|
|
185
|
-
<Tooltip.Arrow />
|
|
186
|
-
</Tooltip.Content>
|
|
187
|
-
</Tooltip.Portal>
|
|
188
|
-
</Tooltip.Root>
|
|
189
|
-
</>
|
|
37
|
+
<div className='flex p-[2px] justify-center items-center'>
|
|
38
|
+
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
39
|
+
<rect x={0} y={0} width={size} height={size} fill={`var(--dx-${value}Fill)`} strokeWidth={4} />
|
|
40
|
+
</svg>
|
|
41
|
+
</div>
|
|
190
42
|
);
|
|
191
43
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
|
+
|
|
10
|
+
import { Toolbar } from '@dxos/react-ui';
|
|
11
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
12
|
+
|
|
13
|
+
import { IconPicker, type IconPickerProps } from './IconPicker';
|
|
14
|
+
|
|
15
|
+
const ToolbarStory = (props: IconPickerProps) => {
|
|
16
|
+
const [icon, setIcon] = useState<string | undefined>(props.value ?? props.defaultValue);
|
|
17
|
+
console.log(icon);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Toolbar.Root>
|
|
21
|
+
<IconPicker {...props} value={icon} onChange={setIcon} onReset={() => setIcon(undefined)} />
|
|
22
|
+
</Toolbar.Root>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const meta: Meta = {
|
|
27
|
+
title: 'ui/react-ui-pickers/IconPicker',
|
|
28
|
+
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
29
|
+
parameters: {
|
|
30
|
+
layout: 'centered',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
export const Default: StoryObj<IconPickerProps> = {
|
|
37
|
+
render: ToolbarStory,
|
|
38
|
+
args: {},
|
|
39
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type ButtonProps, Icon, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { ToolbarPickerButton, type ToolbarPickerProps } from './ToolbarPicker';
|
|
10
|
+
|
|
11
|
+
export type IconPickerProps = {
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
value?: string;
|
|
15
|
+
onChange?: (nextHue: string) => void;
|
|
16
|
+
onReset?: ButtonProps['onClick'];
|
|
17
|
+
} & Pick<ToolbarPickerProps, 'disabled' | 'defaultValue' | 'value' | 'onChange' | 'onReset'>;
|
|
18
|
+
|
|
19
|
+
export const IconPicker = ({ ...props }: ThemedClassName<IconPickerProps>) => {
|
|
20
|
+
const { t } = useTranslation('os');
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<ToolbarPickerButton
|
|
24
|
+
Component={IconPreview}
|
|
25
|
+
label={t('select icon label')}
|
|
26
|
+
icon='ph--selection--regular'
|
|
27
|
+
values={iconValues}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const IconPreview = ({ value }: { value: string }) => {
|
|
34
|
+
return <Icon icon={`ph--${value}--regular`} size={5} />;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* https://phosphoricons.com
|
|
39
|
+
* NOTE: Select icons that we are unlikely to use for the UI.
|
|
40
|
+
*/
|
|
41
|
+
const icons = [
|
|
42
|
+
'ph--air-traffic-control--regular',
|
|
43
|
+
'ph--asterisk--regular',
|
|
44
|
+
'ph--atom--regular',
|
|
45
|
+
'ph--basketball--regular',
|
|
46
|
+
'ph--butterfly--regular',
|
|
47
|
+
'ph--cactus--regular',
|
|
48
|
+
'ph--cake--regular',
|
|
49
|
+
'ph--calendar-dots--regular',
|
|
50
|
+
'ph--campfire--regular',
|
|
51
|
+
'ph--command--regular',
|
|
52
|
+
'ph--confetti--regular',
|
|
53
|
+
'ph--detective--regular',
|
|
54
|
+
'ph--disco-ball--regular',
|
|
55
|
+
'ph--dna--regular',
|
|
56
|
+
'ph--factory--regular',
|
|
57
|
+
'ph--flag-banner-fold--regular',
|
|
58
|
+
'ph--flask--regular',
|
|
59
|
+
'ph--flower-lotus--regular',
|
|
60
|
+
'ph--flying-saucer--regular',
|
|
61
|
+
'ph--game-controller--regular',
|
|
62
|
+
'ph--gavel--regular',
|
|
63
|
+
'ph--gift--regular',
|
|
64
|
+
'ph--guitar--regular',
|
|
65
|
+
'ph--hamburger--regular',
|
|
66
|
+
'ph--handshake--regular',
|
|
67
|
+
'ph--heart--regular',
|
|
68
|
+
'ph--lightbulb--regular',
|
|
69
|
+
'ph--lock--regular',
|
|
70
|
+
'ph--martini--regular',
|
|
71
|
+
'ph--medal-military--regular',
|
|
72
|
+
'ph--moped-front--regular',
|
|
73
|
+
'ph--office-chair--regular',
|
|
74
|
+
'ph--paint-brush-household--regular',
|
|
75
|
+
'ph--peace--regular',
|
|
76
|
+
'ph--person-simple-hike--regular',
|
|
77
|
+
'ph--piggy-bank--regular',
|
|
78
|
+
'ph--potted-plant--regular',
|
|
79
|
+
'ph--radioactive--regular',
|
|
80
|
+
'ph--rocket-launch--regular',
|
|
81
|
+
'ph--shield-star--regular',
|
|
82
|
+
'ph--shopping-cart--regular',
|
|
83
|
+
'ph--stethoscope--regular',
|
|
84
|
+
'ph--student--regular',
|
|
85
|
+
'ph--sun--regular',
|
|
86
|
+
'ph--tote--regular',
|
|
87
|
+
'ph--tree--regular',
|
|
88
|
+
'ph--users-three--regular',
|
|
89
|
+
'ph--yin-yang--regular',
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const iconValues = icons.map((icon) => icon.match(/ph--(.+)--regular/)?.[1] ?? icon);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
6
|
+
import React, { type FC, useEffect, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { DropdownMenu, Icon, type ThemedClassName, Toolbar, Tooltip } from '@dxos/react-ui';
|
|
9
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
10
|
+
|
|
11
|
+
export type ToolbarPickerProps = {
|
|
12
|
+
Component: FC<{ value: string }>;
|
|
13
|
+
label: string;
|
|
14
|
+
icon: string;
|
|
15
|
+
values: string[];
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
value?: string;
|
|
19
|
+
onChange?: (value: string) => void;
|
|
20
|
+
onReset?: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ToolbarPickerButton = ({
|
|
24
|
+
Component,
|
|
25
|
+
disabled,
|
|
26
|
+
classNames,
|
|
27
|
+
defaultValue: _defaultValue,
|
|
28
|
+
value: _value,
|
|
29
|
+
values,
|
|
30
|
+
label,
|
|
31
|
+
icon,
|
|
32
|
+
onChange,
|
|
33
|
+
onReset,
|
|
34
|
+
}: ThemedClassName<ToolbarPickerProps>) => {
|
|
35
|
+
const [value, setValue] = useControllableState<string>({
|
|
36
|
+
prop: _value,
|
|
37
|
+
defaultProp: _defaultValue,
|
|
38
|
+
onChange,
|
|
39
|
+
});
|
|
40
|
+
// TODO(burdon): useControllableState doesn't update the prop when the value is changed. Replace it.
|
|
41
|
+
useEffect(() => setValue(_value), [_value]);
|
|
42
|
+
|
|
43
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
44
|
+
|
|
45
|
+
const suppressNextTooltip = useRef<boolean>(false);
|
|
46
|
+
const [triggerTooltipOpen, setTriggerTooltipOpen] = useState(false);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Tooltip.Root
|
|
50
|
+
open={triggerTooltipOpen}
|
|
51
|
+
onOpenChange={(nextOpen) => {
|
|
52
|
+
if (suppressNextTooltip.current) {
|
|
53
|
+
setTriggerTooltipOpen(false);
|
|
54
|
+
suppressNextTooltip.current = false;
|
|
55
|
+
} else {
|
|
56
|
+
setTriggerTooltipOpen(nextOpen);
|
|
57
|
+
}
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<DropdownMenu.Root
|
|
61
|
+
modal={false}
|
|
62
|
+
open={open}
|
|
63
|
+
onOpenChange={(nextOpen) => {
|
|
64
|
+
setOpen(nextOpen);
|
|
65
|
+
suppressNextTooltip.current = true;
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<Tooltip.Trigger asChild>
|
|
69
|
+
<DropdownMenu.Trigger asChild>
|
|
70
|
+
<Toolbar.Button classNames={mx('gap-2 plb-1', classNames)} disabled={disabled}>
|
|
71
|
+
<span className='sr-only'>{label}</span>
|
|
72
|
+
{(value && <Component value={value} />) || <Icon icon={icon} size={5} />}
|
|
73
|
+
</Toolbar.Button>
|
|
74
|
+
</DropdownMenu.Trigger>
|
|
75
|
+
</Tooltip.Trigger>
|
|
76
|
+
<Tooltip.Portal>
|
|
77
|
+
<Tooltip.Content side='bottom'>
|
|
78
|
+
{label}
|
|
79
|
+
<Tooltip.Arrow />
|
|
80
|
+
</Tooltip.Content>
|
|
81
|
+
</Tooltip.Portal>
|
|
82
|
+
<DropdownMenu.Portal>
|
|
83
|
+
<DropdownMenu.Content side='bottom' classNames='!w-40'>
|
|
84
|
+
<DropdownMenu.Viewport classNames='grid grid-cols-6'>
|
|
85
|
+
{values.map((_value) => {
|
|
86
|
+
return (
|
|
87
|
+
<DropdownMenu.CheckboxItem
|
|
88
|
+
key={_value}
|
|
89
|
+
checked={_value === value}
|
|
90
|
+
onCheckedChange={() => setValue(_value)}
|
|
91
|
+
classNames={'!p-0 items-center justify-center'}
|
|
92
|
+
>
|
|
93
|
+
<Component value={_value} />
|
|
94
|
+
</DropdownMenu.CheckboxItem>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
{onReset && (
|
|
98
|
+
<DropdownMenu.CheckboxItem
|
|
99
|
+
onCheckedChange={() => onReset()}
|
|
100
|
+
classNames={'!p-0 items-center justify-center'}
|
|
101
|
+
>
|
|
102
|
+
<Icon icon='ph--x--regular' size={5} />
|
|
103
|
+
</DropdownMenu.CheckboxItem>
|
|
104
|
+
)}
|
|
105
|
+
</DropdownMenu.Viewport>
|
|
106
|
+
<DropdownMenu.Arrow />
|
|
107
|
+
</DropdownMenu.Content>
|
|
108
|
+
</DropdownMenu.Portal>
|
|
109
|
+
</DropdownMenu.Root>
|
|
110
|
+
</Tooltip.Root>
|
|
111
|
+
);
|
|
112
|
+
};
|