@likec4/diagram 1.17.0 → 1.18.0
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/package.json +24 -18
- package/src/LikeC4Diagram.props.ts +27 -12
- package/src/LikeC4Diagram.tsx +14 -9
- package/src/LikeC4Search.tsx +149 -0
- package/src/StaticLikeC4Diagram.tsx +1 -0
- package/src/hooks/useDiagramState.ts +8 -3
- package/src/hooks/useXYFlow.ts +2 -1
- package/src/likec4model/useLikeC4ElementsTree.ts +6 -13
- package/src/likec4model/useLikeC4Model.ts +21 -11
- package/src/overlays/Overlays.tsx +96 -63
- package/src/overlays/edge-details/EdgeDetailsXYFlow.tsx +3 -1
- package/src/overlays/edge-details/_types.ts +7 -3
- package/src/overlays/edge-details/use-layouted-edge-details.ts +21 -14
- package/src/overlays/edge-details/xyflow/ElementNode.tsx +4 -2
- package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +4 -4
- package/src/overlays/element-details/ElementDetailsCard.tsx +94 -81
- package/src/overlays/element-details/TabPanelDeployments.css.ts +69 -0
- package/src/overlays/element-details/TabPanelDeployments.tsx +217 -0
- package/src/overlays/element-details/TabPanelRelationships.tsx +8 -9
- package/src/overlays/element-details/TabPanelStructure.tsx +10 -10
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +1 -1
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +3 -11
- package/src/overlays/relationships-of/SelectElement.tsx +2 -2
- package/src/overlays/relationships-of/_types.ts +2 -2
- package/src/overlays/relationships-of/use-layouted-relationships.ts +18 -19
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +1 -3
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +12 -6
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +0 -1
- package/src/state/aligners.ts +473 -0
- package/src/state/diagramStore.layout.ts +67 -0
- package/src/state/diagramStore.ts +31 -77
- package/src/ui/DiagramTitlePanel.tsx +2 -29
- package/src/ui/Link.tsx +40 -0
- package/src/ui/notation/NotationPanel.tsx +4 -2
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +2 -2
- package/src/ui/top-left/ManualLayoutToolsButton.tsx +15 -0
- package/src/ui/top-left/TopLeftPanel.tsx +16 -3
- package/src/xyflow/edges/EdgeLabel.tsx +4 -4
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +20 -7
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +28 -13
- package/src/xyflow/nodes/compound/CompoundNode.tsx +51 -19
- package/src/xyflow/nodes/element/ElementNode.tsx +153 -92
- package/src/xyflow/nodes/element/element.css.ts +53 -11
- package/src/xyflow/nodes/shared/ElementIcon.tsx +1 -1
- package/src/xyflow/nodes/shared/Toolbar.tsx +24 -8
- package/src/xyflow/useLayoutConstraints.ts +1 -1
- package/src/xyflow/useXYFlowEvents.ts +18 -5
- package/src/ui/OptionsPanel.css.ts +0 -9
- package/src/ui/OptionsPanel.tsx +0 -70
- package/src/ui/options/EdgeOptions.tsx +0 -142
- package/src/ui/options/NodeOptions.tsx +0 -362
- package/src/xyflow/nodes/element/ElementDetails.css.ts +0 -101
- package/src/xyflow/nodes/element/ElementDetails.tsx +0 -393
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type BorderStyle,
|
|
3
3
|
defaultTheme,
|
|
4
|
+
DiagramNode,
|
|
4
5
|
type Element,
|
|
5
6
|
ElementShapes,
|
|
6
7
|
type Fqn,
|
|
@@ -40,6 +41,7 @@ import { IconCheck, IconFileSymlink, IconSelector, IconTransform } from '@tabler
|
|
|
40
41
|
import { NodeToolbar, type NodeToolbarProps } from '@xyflow/react'
|
|
41
42
|
import clsx from 'clsx'
|
|
42
43
|
import { useEffect, useState } from 'react'
|
|
44
|
+
import type { MergeExclusive } from 'type-fest'
|
|
43
45
|
import { useDiagramState, useDiagramStoreApi, useMantinePortalProps, useUpdateEffect } from '../../../hooks'
|
|
44
46
|
import { stopPropagation } from '../../utils'
|
|
45
47
|
|
|
@@ -75,7 +77,7 @@ const Tooltip = MantineTooltip.withProps({
|
|
|
75
77
|
})
|
|
76
78
|
|
|
77
79
|
type ToolbarProps = NodeToolbarProps & {
|
|
78
|
-
element:
|
|
80
|
+
element: DiagramNode
|
|
79
81
|
onColorPreview: (color: ThemeColor | null) => void
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -86,13 +88,15 @@ export function CompoundToolbar({
|
|
|
86
88
|
onColorPreview,
|
|
87
89
|
...props
|
|
88
90
|
}: ToolbarProps) {
|
|
91
|
+
const modelRef = DiagramNode.modelRef(element)
|
|
92
|
+
const deploymentRef = DiagramNode.deploymentRef(element)
|
|
89
93
|
const targets = [element.id] as NonEmptyArray<Fqn>
|
|
90
94
|
const diagramApi = useDiagramStoreApi()
|
|
91
95
|
const {
|
|
92
96
|
hasGoToSource,
|
|
93
97
|
enableRelationshipBrowser
|
|
94
98
|
} = useDiagramState(s => ({
|
|
95
|
-
hasGoToSource: !!s.
|
|
99
|
+
hasGoToSource: !!s.onOpenSource,
|
|
96
100
|
enableRelationshipBrowser: s.enableRelationshipBrowser
|
|
97
101
|
}))
|
|
98
102
|
|
|
@@ -117,7 +121,8 @@ export function CompoundToolbar({
|
|
|
117
121
|
elementBorderStyle={element.style?.border}
|
|
118
122
|
onChange={onChange}
|
|
119
123
|
/>
|
|
120
|
-
{hasGoToSource && <GoToSourceButton elementId={
|
|
124
|
+
{hasGoToSource && modelRef && <GoToSourceButton elementId={modelRef} />}
|
|
125
|
+
{hasGoToSource && !modelRef && deploymentRef && <GoToSourceButton deploymentId={deploymentRef} />}
|
|
121
126
|
{enableRelationshipBrowser && <BrowseRelationshipsButton elementId={element.id} />}
|
|
122
127
|
</Toolbar>
|
|
123
128
|
)
|
|
@@ -156,13 +161,15 @@ export function ElementToolbar({
|
|
|
156
161
|
onColorPreview,
|
|
157
162
|
...props
|
|
158
163
|
}: ToolbarProps) {
|
|
164
|
+
const modelRef = DiagramNode.modelRef(element)
|
|
165
|
+
const deploymentRef = DiagramNode.deploymentRef(element)
|
|
159
166
|
const targets = [element.id] as NonEmptyArray<Fqn>
|
|
160
167
|
const diagramApi = useDiagramStoreApi()
|
|
161
168
|
const {
|
|
162
169
|
hasGoToSource,
|
|
163
170
|
enableRelationshipBrowser
|
|
164
171
|
} = useDiagramState(s => ({
|
|
165
|
-
hasGoToSource: !!s.
|
|
172
|
+
hasGoToSource: !!s.onOpenSource,
|
|
166
173
|
enableRelationshipBrowser: s.enableRelationshipBrowser
|
|
167
174
|
}))
|
|
168
175
|
const portalProps = useMantinePortalProps()
|
|
@@ -234,20 +241,21 @@ export function ElementToolbar({
|
|
|
234
241
|
onChange={onChange}
|
|
235
242
|
position="right-end"
|
|
236
243
|
/>
|
|
237
|
-
{hasGoToSource && <GoToSourceButton elementId={
|
|
244
|
+
{hasGoToSource && modelRef && <GoToSourceButton elementId={modelRef} />}
|
|
245
|
+
{hasGoToSource && !modelRef && deploymentRef && <GoToSourceButton deploymentId={deploymentRef} />}
|
|
238
246
|
{enableRelationshipBrowser && <BrowseRelationshipsButton elementId={element.id} />}
|
|
239
247
|
</Toolbar>
|
|
240
248
|
)
|
|
241
249
|
}
|
|
242
250
|
|
|
243
251
|
type ColorButtonProps = Omit<PopoverProps, 'onChange'> & {
|
|
244
|
-
element:
|
|
252
|
+
element: DiagramNode
|
|
245
253
|
isOpacityEditable?: boolean
|
|
246
254
|
onColorPreview: (color: ThemeColor | null) => void
|
|
247
255
|
onChange: OnStyleChange
|
|
248
256
|
}
|
|
249
257
|
|
|
250
|
-
function GoToSourceButton({ elementId }
|
|
258
|
+
function GoToSourceButton(props: MergeExclusive<{ elementId: Fqn }, { deploymentId: Fqn }>) {
|
|
251
259
|
const diagramApi = useDiagramStoreApi()
|
|
252
260
|
const portalProps = useMantinePortalProps()
|
|
253
261
|
return (
|
|
@@ -258,7 +266,15 @@ function GoToSourceButton({ elementId }: { elementId: Fqn }) {
|
|
|
258
266
|
color="gray"
|
|
259
267
|
onClick={e => {
|
|
260
268
|
e.stopPropagation()
|
|
261
|
-
|
|
269
|
+
if (props.elementId) {
|
|
270
|
+
diagramApi.getState().onOpenSource?.({
|
|
271
|
+
element: props.elementId
|
|
272
|
+
})
|
|
273
|
+
} else if (props.deploymentId) {
|
|
274
|
+
diagramApi.getState().onOpenSource?.({
|
|
275
|
+
deployment: props.deploymentId
|
|
276
|
+
})
|
|
277
|
+
}
|
|
262
278
|
}}>
|
|
263
279
|
<IconFileSymlink stroke={1.8} style={{ width: '70%' }} />
|
|
264
280
|
</ActionIcon>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type NodeId, type NonEmptyArray, nonNullable } from '@likec4/core'
|
|
2
2
|
import type { InternalNode, NodeChange, ReactFlowProps, XYPosition } from '@xyflow/react'
|
|
3
3
|
import { getNodeDimensions } from '@xyflow/system'
|
|
4
4
|
import { useMemo, useRef } from 'react'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DiagramNode } from '@likec4/core'
|
|
1
2
|
import type { ReactFlowProps } from '@xyflow/react'
|
|
2
3
|
import { useMemo, useRef } from 'react'
|
|
3
4
|
import { isNonNullish, isTruthy } from 'remeda'
|
|
@@ -123,9 +124,11 @@ export function useXYFlowEvents() {
|
|
|
123
124
|
nodesSelectable,
|
|
124
125
|
enableElementDetails,
|
|
125
126
|
setLastClickedNode,
|
|
126
|
-
|
|
127
|
+
onOpenSource,
|
|
127
128
|
openOverlay
|
|
128
129
|
} = diagramApi.getState()
|
|
130
|
+
const modelRef = DiagramNode.modelRef(xynode.data.element)
|
|
131
|
+
const deploymentRef = DiagramNode.deploymentRef(xynode.data.element)
|
|
129
132
|
setLastClickedNode(xynode.id)
|
|
130
133
|
// if we focused on a node, and clicked on another node - focus on the clicked node
|
|
131
134
|
const shallChangeFocus = !!focusedNodeId && focusedNodeId !== xynode.id
|
|
@@ -134,8 +137,16 @@ export function useXYFlowEvents() {
|
|
|
134
137
|
|
|
135
138
|
let stopPropagation = false
|
|
136
139
|
|
|
137
|
-
if (clickedRecently && !!
|
|
138
|
-
|
|
140
|
+
if (clickedRecently && !!onOpenSource && (modelRef || deploymentRef)) {
|
|
141
|
+
if (modelRef) {
|
|
142
|
+
onOpenSource({
|
|
143
|
+
element: modelRef
|
|
144
|
+
})
|
|
145
|
+
} else if (deploymentRef) {
|
|
146
|
+
onOpenSource({
|
|
147
|
+
deployment: deploymentRef
|
|
148
|
+
})
|
|
149
|
+
}
|
|
139
150
|
stopPropagation = true
|
|
140
151
|
}
|
|
141
152
|
|
|
@@ -152,7 +163,7 @@ export function useXYFlowEvents() {
|
|
|
152
163
|
stopPropagation = true
|
|
153
164
|
break
|
|
154
165
|
}
|
|
155
|
-
case !clickedRecently && focusedNodeId === xynode.id && enableElementDetails: {
|
|
166
|
+
case !clickedRecently && modelRef && focusedNodeId === xynode.id && enableElementDetails: {
|
|
156
167
|
openOverlay({
|
|
157
168
|
elementDetails: xynode.data.element.id
|
|
158
169
|
})
|
|
@@ -160,7 +171,9 @@ export function useXYFlowEvents() {
|
|
|
160
171
|
break
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
|
-
} else if (
|
|
174
|
+
} else if (
|
|
175
|
+
enableElementDetails && modelRef && (clickedRecently || focusedNodeId === xynode.id) && !onNodeClick
|
|
176
|
+
) {
|
|
164
177
|
openOverlay({
|
|
165
178
|
elementDetails: xynode.data.element.id
|
|
166
179
|
})
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { fallbackVar, style } from '@vanilla-extract/css'
|
|
2
|
-
import { mantine, vars } from '../theme-vars'
|
|
3
|
-
|
|
4
|
-
export const panel = style({
|
|
5
|
-
top: fallbackVar(vars.optionsPanel.top, '1rem'),
|
|
6
|
-
right: fallbackVar(vars.optionsPanel.right, '1rem'),
|
|
7
|
-
margin: 0,
|
|
8
|
-
boxShadow: mantine.shadows.xl
|
|
9
|
-
})
|
package/src/ui/OptionsPanel.tsx
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Card } from '@mantine/core'
|
|
2
|
-
import { useOnSelectionChange } from '@xyflow/react'
|
|
3
|
-
import clsx from 'clsx'
|
|
4
|
-
import { deepEqual as eq } from 'fast-equals'
|
|
5
|
-
import { AnimatePresence, m } from 'framer-motion'
|
|
6
|
-
import { useState } from 'react'
|
|
7
|
-
import { useDiagramState } from '../hooks/useDiagramState'
|
|
8
|
-
import { NodeOptions } from './options/NodeOptions'
|
|
9
|
-
import * as styles from './OptionsPanel.css'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @deprecated
|
|
13
|
-
*/
|
|
14
|
-
export function OptionsPanel() {
|
|
15
|
-
const { isFocused, viewId } = useDiagramState(s => ({
|
|
16
|
-
viewId: s.view.id,
|
|
17
|
-
isFocused: s.focusedNodeId !== null || s.activeWalkthrough !== null
|
|
18
|
-
}))
|
|
19
|
-
const [selected, setSelected] = useState<{
|
|
20
|
-
nodes: string[]
|
|
21
|
-
edges: string[]
|
|
22
|
-
}>({
|
|
23
|
-
nodes: [],
|
|
24
|
-
edges: []
|
|
25
|
-
})
|
|
26
|
-
useOnSelectionChange({
|
|
27
|
-
onChange: ({ nodes, edges }) => {
|
|
28
|
-
const newSelected = {
|
|
29
|
-
nodes: nodes.map(n => n.id).sort(),
|
|
30
|
-
edges: edges.map(e => e.id).sort()
|
|
31
|
-
}
|
|
32
|
-
if (!eq(selected, newSelected)) {
|
|
33
|
-
setSelected(newSelected)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
const { nodes } = selected
|
|
38
|
-
// const hasAnySelection = !isFocused && (nodes.length > 0 || edges.length > 0)
|
|
39
|
-
const hasAnySelection = !isFocused && nodes.length > 0
|
|
40
|
-
return (
|
|
41
|
-
<AnimatePresence mode="wait">
|
|
42
|
-
{hasAnySelection && (
|
|
43
|
-
<m.div
|
|
44
|
-
key={viewId}
|
|
45
|
-
initial={{ opacity: 0, scale: 0.85 }}
|
|
46
|
-
animate={{ opacity: 1, scale: 1 }}
|
|
47
|
-
exit={{
|
|
48
|
-
opacity: 0,
|
|
49
|
-
scale: 0.85
|
|
50
|
-
}}
|
|
51
|
-
transition={{ duration: 0.14 }}
|
|
52
|
-
className={clsx('react-flow__panel', styles.panel)}
|
|
53
|
-
style={{
|
|
54
|
-
transformOrigin: 'center right',
|
|
55
|
-
maxWidth: '320px'
|
|
56
|
-
}}
|
|
57
|
-
>
|
|
58
|
-
<Card shadow="md">
|
|
59
|
-
{nodes.length > 0 && <NodeOptions selectedNodeIds={nodes} />}
|
|
60
|
-
{
|
|
61
|
-
/* nodes.length === 0 && edges.length > 0 && (
|
|
62
|
-
<EdgeOptions selectedEdgeIds={edges} />
|
|
63
|
-
) */
|
|
64
|
-
}
|
|
65
|
-
</Card>
|
|
66
|
-
</m.div>
|
|
67
|
-
)}
|
|
68
|
-
</AnimatePresence>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { DefaultRelationshipColor, defaultTheme, type NonEmptyArray, type ThemeColor } from '@likec4/core'
|
|
2
|
-
import { CheckIcon, ColorSwatch, Divider, Flex, rem, Stack, Text, Tooltip, TooltipGroup } from '@mantine/core'
|
|
3
|
-
import { shallowEqual } from 'fast-equals'
|
|
4
|
-
import { memo } from 'react'
|
|
5
|
-
import { hasAtLeast, keys, takeWhile } from 'remeda'
|
|
6
|
-
import { useDiagramStoreApi, useXYEdgesData } from '../../hooks'
|
|
7
|
-
import { type XYFlowEdge } from '../../xyflow/types'
|
|
8
|
-
|
|
9
|
-
const {
|
|
10
|
-
primary,
|
|
11
|
-
secondary,
|
|
12
|
-
muted,
|
|
13
|
-
...otherColors
|
|
14
|
-
} = defaultTheme.elements
|
|
15
|
-
|
|
16
|
-
export const themedColors = [
|
|
17
|
-
{ key: 'primary', value: primary.fill },
|
|
18
|
-
{ key: 'secondary', value: secondary.fill },
|
|
19
|
-
{ key: 'muted', value: muted.fill }
|
|
20
|
-
] satisfies Array<{ key: ThemeColor; value: string }>
|
|
21
|
-
|
|
22
|
-
export const colors = keys(otherColors).map(key => ({
|
|
23
|
-
key,
|
|
24
|
-
value: defaultTheme.elements[key].fill
|
|
25
|
-
}))
|
|
26
|
-
|
|
27
|
-
export type ThemeColorKey = typeof themedColors[0]['key']
|
|
28
|
-
export type ColorKey = typeof colors[0]['key']
|
|
29
|
-
|
|
30
|
-
type XYEdgeData = Pick<XYFlowEdge, 'id' | 'data'>
|
|
31
|
-
|
|
32
|
-
export const EdgeOptions = memo<{ selectedEdgeIds: string[] }>(({ selectedEdgeIds }) => {
|
|
33
|
-
const diagramApi = useDiagramStoreApi()
|
|
34
|
-
const edges = useXYEdgesData(selectedEdgeIds)
|
|
35
|
-
if (!hasAtLeast(edges, 1)) {
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
38
|
-
if (edges.length !== selectedEdgeIds.length) {
|
|
39
|
-
throw new Error('EdgeOptions: edges and props.edges should have the same length')
|
|
40
|
-
}
|
|
41
|
-
const [firstEdge, ...rest] = edges
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<Stack gap={'xs'}>
|
|
45
|
-
<div>
|
|
46
|
-
<Text fz={rem(9)} fw={'500'} c={'dimmed'}>
|
|
47
|
-
RELATIONSHIP{rest.length > 0 ? 'S' : ''}
|
|
48
|
-
</Text>
|
|
49
|
-
<Text size="xs" c={rest.length > 0 ? 'dimmed' : ''} truncate>
|
|
50
|
-
{rest.length === 0 ? firstEdge.data.edge.label : `[ multiple ]`}
|
|
51
|
-
</Text>
|
|
52
|
-
</div>
|
|
53
|
-
{
|
|
54
|
-
/* {showShapeOption && (
|
|
55
|
-
<ShapeOption
|
|
56
|
-
nodes={edges}
|
|
57
|
-
onShapeChange={(shape: ElementShape) => {
|
|
58
|
-
triggerChange({ shape })
|
|
59
|
-
}} />
|
|
60
|
-
)} */
|
|
61
|
-
}
|
|
62
|
-
<Colors
|
|
63
|
-
edges={edges}
|
|
64
|
-
onColorChange={(_color: ColorKey | ThemeColorKey) => {
|
|
65
|
-
// triggerChange({ color })
|
|
66
|
-
}} />
|
|
67
|
-
</Stack>
|
|
68
|
-
)
|
|
69
|
-
}, (prevProps, nextProps) => shallowEqual(prevProps.selectedEdgeIds, nextProps.selectedEdgeIds))
|
|
70
|
-
|
|
71
|
-
function Colors({
|
|
72
|
-
edges: [firstEdge, ...edges],
|
|
73
|
-
onColorChange
|
|
74
|
-
}: {
|
|
75
|
-
edges: NonEmptyArray<XYEdgeData>
|
|
76
|
-
onColorChange: (color: ColorKey | ThemeColorKey) => void
|
|
77
|
-
}) {
|
|
78
|
-
let selectedColor = (firstEdge.data.edge.color ?? DefaultRelationshipColor) as ThemeColor | null
|
|
79
|
-
takeWhile(edges, edge => {
|
|
80
|
-
const color = edge.data.edge.color ?? DefaultRelationshipColor
|
|
81
|
-
if (color !== selectedColor) {
|
|
82
|
-
selectedColor = null
|
|
83
|
-
return false
|
|
84
|
-
}
|
|
85
|
-
return true
|
|
86
|
-
})
|
|
87
|
-
const changeColor = (color: ColorKey | ThemeColorKey) => () => {
|
|
88
|
-
if (selectedColor === color) {
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
onColorChange(color)
|
|
92
|
-
}
|
|
93
|
-
return (
|
|
94
|
-
<div>
|
|
95
|
-
<TooltipGroup openDelay={400} closeDelay={300}>
|
|
96
|
-
<Divider label="color" labelPosition="left" />
|
|
97
|
-
<Flex mt={'xs'} maw={150} gap="xs" justify="flex-start" align="flex-start" direction="row" wrap="wrap">
|
|
98
|
-
{themedColors.map(({ key, value }) => (
|
|
99
|
-
<Tooltip
|
|
100
|
-
key={key}
|
|
101
|
-
label={key}
|
|
102
|
-
fz={'xs'}
|
|
103
|
-
color="dark"
|
|
104
|
-
offset={2}
|
|
105
|
-
transitionProps={{ duration: 140, transition: 'slide-up' }}>
|
|
106
|
-
<ColorSwatch
|
|
107
|
-
color={value}
|
|
108
|
-
size={20}
|
|
109
|
-
withShadow
|
|
110
|
-
onClick={changeColor(key)}
|
|
111
|
-
style={{ color: '#fff', cursor: 'pointer' }}
|
|
112
|
-
>
|
|
113
|
-
{selectedColor === key && <CheckIcon style={{ width: rem(12), height: rem(12) }} />}
|
|
114
|
-
</ColorSwatch>
|
|
115
|
-
</Tooltip>
|
|
116
|
-
))}
|
|
117
|
-
</Flex>
|
|
118
|
-
|
|
119
|
-
<Flex mt="sm" maw={150} gap="xs" justify="flex-start" align="flex-start" direction="row" wrap="wrap">
|
|
120
|
-
{colors.map(({ key, value }) => (
|
|
121
|
-
<Tooltip
|
|
122
|
-
key={key}
|
|
123
|
-
label={key}
|
|
124
|
-
fz={'xs'}
|
|
125
|
-
color="dark"
|
|
126
|
-
offset={2}
|
|
127
|
-
transitionProps={{ duration: 140, transition: 'slide-up' }}>
|
|
128
|
-
<ColorSwatch
|
|
129
|
-
color={value}
|
|
130
|
-
size={20}
|
|
131
|
-
onClick={changeColor(key)}
|
|
132
|
-
style={{ color: '#fff', cursor: 'pointer' }}
|
|
133
|
-
>
|
|
134
|
-
{selectedColor === key && <CheckIcon style={{ width: rem(12), height: rem(12) }} />}
|
|
135
|
-
</ColorSwatch>
|
|
136
|
-
</Tooltip>
|
|
137
|
-
))}
|
|
138
|
-
</Flex>
|
|
139
|
-
</TooltipGroup>
|
|
140
|
-
</div>
|
|
141
|
-
)
|
|
142
|
-
}
|