@noya-app/noya-designsystem 0.1.41 → 0.1.43
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +16 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +268 -147
- package/dist/index.d.ts +268 -147
- package/dist/index.js +7618 -6908
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6161 -5487
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
- package/src/__tests__/combobox.test.ts +578 -0
- package/src/components/ActivityIndicator.tsx +4 -4
- package/src/components/AnimatePresence.tsx +5 -5
- package/src/components/Avatar.tsx +5 -2
- package/src/components/BaseToolbar.tsx +61 -0
- package/src/components/Button.tsx +1 -1
- package/src/components/Checkbox.tsx +6 -5
- package/src/components/Combobox.tsx +327 -337
- package/src/components/ComboboxMenu.tsx +88 -0
- package/src/components/Dialog.tsx +10 -6
- package/src/components/DimensionInput.tsx +4 -4
- package/src/components/FillPreviewBackground.tsx +51 -44
- package/src/components/FloatingWindow.tsx +5 -2
- package/src/components/GradientPicker.tsx +14 -14
- package/src/components/IconButton.tsx +6 -12
- package/src/components/Icons.tsx +3 -3
- package/src/components/InputField.tsx +145 -160
- package/src/components/Label.tsx +4 -4
- package/src/components/LabeledElementView.tsx +35 -41
- package/src/components/ListView.tsx +49 -39
- package/src/components/Message.tsx +75 -0
- package/src/components/Progress.tsx +2 -2
- package/src/components/ScrollArea.tsx +7 -5
- package/src/components/SegmentedControl.tsx +171 -0
- package/src/components/SelectMenu.tsx +61 -10
- package/src/components/Slider.tsx +5 -2
- package/src/components/Sortable.tsx +17 -27
- package/src/components/Spacer.tsx +28 -9
- package/src/components/Switch.tsx +8 -8
- package/src/components/TextArea.tsx +59 -12
- package/src/components/Toolbar.tsx +129 -0
- package/src/components/Tooltip.tsx +10 -7
- package/src/components/WorkspaceLayout.tsx +145 -152
- package/src/components/internal/Menu.tsx +5 -4
- package/src/contexts/DesignSystemConfiguration.tsx +15 -24
- package/src/contexts/DialogContext.tsx +137 -49
- package/src/contexts/ImageDataContext.tsx +6 -12
- package/src/index.css +1 -3
- package/src/index.tsx +12 -3
- package/src/utils/combobox.ts +369 -0
- package/tailwind.config.ts +1 -2
- package/src/components/RadioGroup.tsx +0 -100
- package/src/utils/completions.ts +0 -21
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Size } from "@noya-app/noya-geometry";
|
|
2
|
+
import React, { ForwardedRef, forwardRef, memo } from "react";
|
|
3
|
+
import { cx } from "../utils/classNames";
|
|
4
|
+
import { InternalComboboxItem } from "../utils/combobox";
|
|
5
|
+
import { fuzzyTokenize } from "../utils/fuzzyScorer";
|
|
6
|
+
import { IVirtualizedList, ListView } from "./ListView";
|
|
7
|
+
import { Spacer } from "./Spacer";
|
|
8
|
+
|
|
9
|
+
interface ComboboxMenuProps {
|
|
10
|
+
items: InternalComboboxItem[];
|
|
11
|
+
selectedIndex: number;
|
|
12
|
+
onSelectItem: (item: InternalComboboxItem) => void;
|
|
13
|
+
onHoverIndex: (index: number) => void;
|
|
14
|
+
listSize: Size;
|
|
15
|
+
style?: React.CSSProperties;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ComboboxMenu = memo(
|
|
19
|
+
forwardRef(function ComboboxMenu(
|
|
20
|
+
{
|
|
21
|
+
items,
|
|
22
|
+
selectedIndex,
|
|
23
|
+
onSelectItem,
|
|
24
|
+
onHoverIndex,
|
|
25
|
+
listSize,
|
|
26
|
+
style,
|
|
27
|
+
}: ComboboxMenuProps,
|
|
28
|
+
forwardedRef: ForwardedRef<IVirtualizedList>
|
|
29
|
+
) {
|
|
30
|
+
return (
|
|
31
|
+
<ListView.Root
|
|
32
|
+
ref={forwardedRef}
|
|
33
|
+
scrollable
|
|
34
|
+
keyExtractor={(item) => item.id}
|
|
35
|
+
data={items}
|
|
36
|
+
virtualized={listSize}
|
|
37
|
+
pressEventName="onPointerDown"
|
|
38
|
+
sectionHeaderVariant="label"
|
|
39
|
+
style={style}
|
|
40
|
+
renderItem={(item, i) => {
|
|
41
|
+
if (item.type === "sectionHeader") {
|
|
42
|
+
return (
|
|
43
|
+
<ListView.Row key={item.id} isSectionHeader>
|
|
44
|
+
{item.name}
|
|
45
|
+
</ListView.Row>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const tokens = fuzzyTokenize({
|
|
50
|
+
item: item.name,
|
|
51
|
+
itemScore: item,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ListView.Row
|
|
56
|
+
key={item.id}
|
|
57
|
+
selected={i === selectedIndex}
|
|
58
|
+
onPress={() => onSelectItem(item)}
|
|
59
|
+
onHoverChange={(hovered) => {
|
|
60
|
+
if (hovered) {
|
|
61
|
+
onHoverIndex(i);
|
|
62
|
+
}
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
{tokens.map((token, j) => (
|
|
66
|
+
<span
|
|
67
|
+
key={`${item.id}-token-${j}`}
|
|
68
|
+
className={cx(
|
|
69
|
+
token.type === "match" ? "font-bold" : "font-normal",
|
|
70
|
+
"whitespace-pre"
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
{token.text}
|
|
74
|
+
</span>
|
|
75
|
+
))}
|
|
76
|
+
{item.icon && (
|
|
77
|
+
<>
|
|
78
|
+
<Spacer.Horizontal />
|
|
79
|
+
{item.icon}
|
|
80
|
+
</>
|
|
81
|
+
)}
|
|
82
|
+
</ListView.Row>
|
|
83
|
+
);
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
})
|
|
88
|
+
);
|
|
@@ -7,9 +7,9 @@ import React, {
|
|
|
7
7
|
useImperativeHandle,
|
|
8
8
|
useRef,
|
|
9
9
|
} from "react";
|
|
10
|
+
import { cx } from "../utils/classNames";
|
|
10
11
|
import { IconButton } from "./IconButton";
|
|
11
12
|
import { Spacer } from "./Spacer";
|
|
12
|
-
import { cx } from "../utils/classNames";
|
|
13
13
|
|
|
14
14
|
const StyledOverlay = forwardRef<
|
|
15
15
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
@@ -99,6 +99,7 @@ interface Props {
|
|
|
99
99
|
typeof DialogPrimitive.Content
|
|
100
100
|
>["onOpenAutoFocus"];
|
|
101
101
|
closeOnInteractOutside?: boolean;
|
|
102
|
+
showCloseButton?: boolean;
|
|
102
103
|
className?: string;
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -113,6 +114,7 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
113
114
|
onOpenAutoFocus,
|
|
114
115
|
closeOnInteractOutside = true,
|
|
115
116
|
className,
|
|
117
|
+
showCloseButton = true,
|
|
116
118
|
}: Props,
|
|
117
119
|
forwardedRef: ForwardedRef<IDialog>
|
|
118
120
|
) {
|
|
@@ -138,11 +140,13 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
138
140
|
onInteractOutside: (event) => event.preventDefault(),
|
|
139
141
|
})}
|
|
140
142
|
>
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
{showCloseButton && (
|
|
144
|
+
<CloseButtonContainer>
|
|
145
|
+
<DialogPrimitive.Close asChild>
|
|
146
|
+
<IconButton iconName="Cross1Icon" />
|
|
147
|
+
</DialogPrimitive.Close>
|
|
148
|
+
</CloseButtonContainer>
|
|
149
|
+
)}
|
|
146
150
|
{title && (
|
|
147
151
|
<>
|
|
148
152
|
<StyledTitle>{title}</StyledTitle>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { round } from "@noya-app/noya-utils";
|
|
2
|
-
import
|
|
2
|
+
import * as React from "react";
|
|
3
3
|
import { InputField } from "./InputField";
|
|
4
4
|
|
|
5
5
|
export type SetNumberMode = "replace" | "adjust";
|
|
@@ -29,7 +29,7 @@ interface Props {
|
|
|
29
29
|
trigger?: "change" | "submit";
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export const DimensionInput = memo(function DimensionInput({
|
|
32
|
+
export const DimensionInput = React.memo(function DimensionInput({
|
|
33
33
|
id,
|
|
34
34
|
value,
|
|
35
35
|
onSetValue,
|
|
@@ -39,12 +39,12 @@ export const DimensionInput = memo(function DimensionInput({
|
|
|
39
39
|
disabled,
|
|
40
40
|
trigger = "submit",
|
|
41
41
|
}: Props) {
|
|
42
|
-
const handleNudgeValue = useCallback(
|
|
42
|
+
const handleNudgeValue = React.useCallback(
|
|
43
43
|
(value: number) => onSetValue(value, "adjust"),
|
|
44
44
|
[onSetValue]
|
|
45
45
|
);
|
|
46
46
|
|
|
47
|
-
const handleSetValue = useCallback(
|
|
47
|
+
const handleSetValue = React.useCallback(
|
|
48
48
|
(value: number) => onSetValue(value, "replace"),
|
|
49
49
|
[onSetValue]
|
|
50
50
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Sketch } from "@noya-app/noya-file-format";
|
|
2
2
|
// import { useSketchImage } from 'noya-renderer';
|
|
3
|
-
import
|
|
3
|
+
import * as React from "react";
|
|
4
4
|
import { useImageData } from "../contexts/ImageDataContext";
|
|
5
5
|
import { useObjectURL } from "../hooks/useObjectURL";
|
|
6
6
|
import { cssVars } from "../theme";
|
|
@@ -14,7 +14,7 @@ const dotsHorizontalSvg = (fillColor: string) => `
|
|
|
14
14
|
</svg>
|
|
15
15
|
`;
|
|
16
16
|
|
|
17
|
-
const Background = memo(function Background({
|
|
17
|
+
const Background = React.memo(function Background({
|
|
18
18
|
background,
|
|
19
19
|
}: {
|
|
20
20
|
background?: string;
|
|
@@ -42,57 +42,64 @@ function getPatternSizeAndPosition(
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export const PatternPreviewBackground = memo(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
45
|
+
export const PatternPreviewBackground = React.memo(
|
|
46
|
+
function PatternPreviewBackground({
|
|
47
|
+
fillType,
|
|
48
|
+
tileScale,
|
|
49
|
+
imageRef,
|
|
50
|
+
}: {
|
|
51
|
+
fillType: Sketch.PatternFillType;
|
|
52
|
+
tileScale: number;
|
|
53
|
+
imageRef: string;
|
|
54
|
+
}) {
|
|
55
|
+
const image = useImageData(imageRef);
|
|
56
|
+
|
|
57
|
+
const url = useObjectURL(image);
|
|
58
|
+
|
|
59
|
+
const size = getPatternSizeAndPosition(fillType, tileScale);
|
|
60
|
+
|
|
61
|
+
const background = React.useMemo(
|
|
62
|
+
() =>
|
|
63
|
+
[
|
|
64
|
+
size,
|
|
65
|
+
`url(${url})`,
|
|
66
|
+
fillType === Sketch.PatternFillType.Tile ? "repeat" : "no-repeat",
|
|
67
|
+
].join(" "),
|
|
68
|
+
[fillType, size, url]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return <Background background={background} />;
|
|
72
|
+
}
|
|
73
|
+
);
|
|
72
74
|
|
|
73
|
-
const ColorPreviewBackground = memo(function ColorPreviewBackground({
|
|
75
|
+
const ColorPreviewBackground = React.memo(function ColorPreviewBackground({
|
|
74
76
|
color,
|
|
75
77
|
}: {
|
|
76
78
|
color: Sketch.Color;
|
|
77
79
|
}) {
|
|
78
|
-
const background = useMemo(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const GradientPreviewBackground = memo(function GradientPreviewBackground({
|
|
84
|
-
gradient,
|
|
85
|
-
}: {
|
|
86
|
-
gradient: Sketch.Gradient;
|
|
87
|
-
}) {
|
|
88
|
-
const background = useMemo(
|
|
89
|
-
() => getGradientBackground(gradient.stops, gradient.gradientType, 180),
|
|
90
|
-
[gradient.gradientType, gradient.stops]
|
|
80
|
+
const background = React.useMemo(
|
|
81
|
+
() => sketchColorToRgbaString(color),
|
|
82
|
+
[color]
|
|
91
83
|
);
|
|
92
84
|
|
|
93
85
|
return <Background background={background} />;
|
|
94
86
|
});
|
|
95
87
|
|
|
88
|
+
const GradientPreviewBackground = React.memo(
|
|
89
|
+
function GradientPreviewBackground({
|
|
90
|
+
gradient,
|
|
91
|
+
}: {
|
|
92
|
+
gradient: Sketch.Gradient;
|
|
93
|
+
}) {
|
|
94
|
+
const background = React.useMemo(
|
|
95
|
+
() => getGradientBackground(gradient.stops, gradient.gradientType, 180),
|
|
96
|
+
[gradient.gradientType, gradient.stops]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return <Background background={background} />;
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
96
103
|
interface Props {
|
|
97
104
|
value?: Sketch.Color | Sketch.Gradient | SketchPattern;
|
|
98
105
|
}
|
|
@@ -101,7 +108,7 @@ const background = `center url("data:image/svg+xml;utf8,${dotsHorizontalSvg(
|
|
|
101
108
|
cssVars.colors.placeholderDots
|
|
102
109
|
)}") no-repeat`;
|
|
103
110
|
|
|
104
|
-
export const FillPreviewBackground = memo(function FillPreviewBackground({
|
|
111
|
+
export const FillPreviewBackground = React.memo(function FillPreviewBackground({
|
|
105
112
|
value,
|
|
106
113
|
}: Props) {
|
|
107
114
|
if (!value) return <Background background={background} />;
|
|
@@ -129,7 +129,9 @@ function defaultRenderToolbar({
|
|
|
129
129
|
{toolbarContent}
|
|
130
130
|
<IconButton
|
|
131
131
|
iconName="Cross3Icon"
|
|
132
|
+
className="h-8 w-8 rounded-full"
|
|
132
133
|
onClick={onClose}
|
|
134
|
+
size={20}
|
|
133
135
|
style={{
|
|
134
136
|
marginLeft: "6px",
|
|
135
137
|
marginRight: "-6px",
|
|
@@ -250,7 +252,7 @@ export const FloatingWindow: React.FC<FloatingWindowProps> = ({
|
|
|
250
252
|
]
|
|
251
253
|
);
|
|
252
254
|
|
|
253
|
-
const handleMouseUp = useCallback((
|
|
255
|
+
const handleMouseUp = useCallback((_e: MouseEvent) => {
|
|
254
256
|
setIsDragging(false);
|
|
255
257
|
setIsResizing(false);
|
|
256
258
|
setResizeDirection(null);
|
|
@@ -285,7 +287,8 @@ export const FloatingWindow: React.FC<FloatingWindowProps> = ({
|
|
|
285
287
|
width: size.width,
|
|
286
288
|
height: size.height,
|
|
287
289
|
outline: `1px solid ${cssVars.colors.divider}`,
|
|
288
|
-
backgroundColor: cssVars.colors.
|
|
290
|
+
backgroundColor: cssVars.colors.background,
|
|
291
|
+
zIndex: cssVars.zIndex.menu,
|
|
289
292
|
}}
|
|
290
293
|
>
|
|
291
294
|
<div
|
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
equalColorObjects,
|
|
10
10
|
hsvaToRgba,
|
|
11
11
|
rgbaToHsva,
|
|
12
|
-
} from
|
|
13
|
-
import type { Sketch } from
|
|
14
|
-
import
|
|
15
|
-
import { Spacer } from
|
|
16
|
-
import { rgbaToSketchColor, sketchColorToRgba } from
|
|
12
|
+
} from "@noya-app/noya-colorpicker";
|
|
13
|
+
import type { Sketch } from "@noya-app/noya-file-format";
|
|
14
|
+
import * as React from "react";
|
|
15
|
+
import { Spacer } from "../components/Spacer";
|
|
16
|
+
import { rgbaToSketchColor, sketchColorToRgba } from "../utils/sketchColor";
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
19
|
value: Sketch.GradientStop[];
|
|
@@ -25,7 +25,7 @@ interface Props {
|
|
|
25
25
|
onSelectStop: (index: number) => void;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export const GradientPicker = memo(function GradientPicker({
|
|
28
|
+
export const GradientPicker = React.memo(function GradientPicker({
|
|
29
29
|
value,
|
|
30
30
|
selectedStop,
|
|
31
31
|
onChangeColor,
|
|
@@ -34,33 +34,33 @@ export const GradientPicker = memo(function GradientPicker({
|
|
|
34
34
|
onDelete,
|
|
35
35
|
onSelectStop,
|
|
36
36
|
}: Props) {
|
|
37
|
-
const colorModel: ColorModel<RgbaColor> = useMemo(
|
|
37
|
+
const colorModel: ColorModel<RgbaColor> = React.useMemo(
|
|
38
38
|
() => ({
|
|
39
39
|
defaultColor: { r: 0, g: 0, b: 0, a: 1 },
|
|
40
40
|
toHsva: rgbaToHsva,
|
|
41
41
|
fromHsva: hsvaToRgba,
|
|
42
42
|
equal: equalColorObjects,
|
|
43
43
|
}),
|
|
44
|
-
[]
|
|
44
|
+
[]
|
|
45
45
|
);
|
|
46
46
|
|
|
47
|
-
const rgbaColor = useMemo(
|
|
47
|
+
const rgbaColor = React.useMemo(
|
|
48
48
|
() => sketchColorToRgba(value[selectedStop].color),
|
|
49
|
-
[value, selectedStop]
|
|
49
|
+
[value, selectedStop]
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
const handleChangeColor = useCallback(
|
|
52
|
+
const handleChangeColor = React.useCallback(
|
|
53
53
|
(value: RgbaColor) => {
|
|
54
54
|
onChangeColor(rgbaToSketchColor(value));
|
|
55
55
|
},
|
|
56
|
-
[onChangeColor]
|
|
56
|
+
[onChangeColor]
|
|
57
57
|
);
|
|
58
58
|
|
|
59
|
-
const handleAddGradientStop = useCallback(
|
|
59
|
+
const handleAddGradientStop = React.useCallback(
|
|
60
60
|
(value: RgbaColor, position: number) => {
|
|
61
61
|
onAdd(rgbaToSketchColor(value), position);
|
|
62
62
|
},
|
|
63
|
-
[onAdd]
|
|
63
|
+
[onAdd]
|
|
64
64
|
);
|
|
65
65
|
|
|
66
66
|
return (
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import React
|
|
2
|
-
CSSProperties,
|
|
3
|
-
ForwardedRef,
|
|
4
|
-
forwardRef,
|
|
5
|
-
memo,
|
|
6
|
-
useMemo,
|
|
7
|
-
} from "react";
|
|
1
|
+
import * as React from "react";
|
|
8
2
|
import { cssVars } from "../theme";
|
|
9
3
|
import { Button, ButtonRootProps } from "./Button";
|
|
10
4
|
import { IconName, Icons } from "./Icons";
|
|
@@ -12,20 +6,20 @@ import { IconName, Icons } from "./Icons";
|
|
|
12
6
|
type Props = Omit<ButtonRootProps, "children" | "variant" | "flex" | "size"> & {
|
|
13
7
|
iconName: IconName;
|
|
14
8
|
className?: string;
|
|
15
|
-
style?: CSSProperties;
|
|
9
|
+
style?: React.CSSProperties;
|
|
16
10
|
selected?: boolean;
|
|
17
11
|
color?: string;
|
|
18
12
|
size?: number;
|
|
19
13
|
};
|
|
20
14
|
|
|
21
|
-
export const IconButton = memo(
|
|
22
|
-
forwardRef(function IconButton(
|
|
15
|
+
export const IconButton = React.memo(
|
|
16
|
+
React.forwardRef(function IconButton(
|
|
23
17
|
{ selected, iconName, color, size, contentStyle, ...props }: Props,
|
|
24
|
-
forwardedRef: ForwardedRef<HTMLButtonElement>
|
|
18
|
+
forwardedRef: React.ForwardedRef<HTMLButtonElement>
|
|
25
19
|
) {
|
|
26
20
|
const Icon = Icons[iconName];
|
|
27
21
|
|
|
28
|
-
const style = useMemo((): CSSProperties => {
|
|
22
|
+
const style = React.useMemo((): React.CSSProperties => {
|
|
29
23
|
return {
|
|
30
24
|
padding: "0 2px",
|
|
31
25
|
...(size && { minHeight: size }),
|
package/src/components/Icons.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as Icons from "@noya-app/noya-icons";
|
|
2
|
-
import
|
|
2
|
+
import * as React from "react";
|
|
3
3
|
|
|
4
4
|
export type IconName = keyof typeof Icons;
|
|
5
5
|
|
|
6
6
|
export { Icons };
|
|
7
7
|
|
|
8
8
|
export function renderIcon(
|
|
9
|
-
iconName: Exclude<ReactNode, string> | IconName
|
|
10
|
-
): ReactNode {
|
|
9
|
+
iconName: Exclude<React.ReactNode, string> | IconName
|
|
10
|
+
): React.ReactNode {
|
|
11
11
|
if (typeof iconName === "string") {
|
|
12
12
|
const Icon = Icons[iconName as IconName];
|
|
13
13
|
|