minka-ds 0.4.2 → 0.4.3
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 +1 -1
- package/src/components/ui/diagram-node.tsx +116 -0
- package/src/components/ui/flow-diagram.tsx +42 -47
- package/src/index.ts +1 -0
- package/tokens/primitives.css +1 -0
- package/tokens/semantic.css +2 -0
package/package.json
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
|
|
6
|
+
export type DiagramNodeVariant = "wallet" | "anchor"
|
|
7
|
+
|
|
8
|
+
/** Brand pair for state nodes (issue/destroy). */
|
|
9
|
+
export type DiagramAccent =
|
|
10
|
+
| "yellow-darkforest"
|
|
11
|
+
| "rose-coral"
|
|
12
|
+
| "blue-navy"
|
|
13
|
+
| "beige-bronze"
|
|
14
|
+
| "gray-black"
|
|
15
|
+
|
|
16
|
+
export interface DiagramNodeProps {
|
|
17
|
+
/** Node type — drives default colors. wallet = white/raised, anchor = inverted slate. */
|
|
18
|
+
variant?: DiagramNodeVariant
|
|
19
|
+
/** When false, renders a dashed empty slot. */
|
|
20
|
+
filled?: boolean
|
|
21
|
+
/** Pair-colored "state" node (issue/destroy). Overrides variant colors. */
|
|
22
|
+
accent?: DiagramAccent
|
|
23
|
+
/** Invert the accent fill (light bg + dark text). */
|
|
24
|
+
accentInverted?: boolean
|
|
25
|
+
/** Compact sizing (used by anchor/alias nodes). */
|
|
26
|
+
compact?: boolean
|
|
27
|
+
className?: string
|
|
28
|
+
children?: React.ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// exact-dash rounded border for the empty state
|
|
32
|
+
function DashedBorder({ radius = 10 }: { radius?: number }) {
|
|
33
|
+
return (
|
|
34
|
+
<svg className="pointer-events-none absolute inset-0 size-full" aria-hidden>
|
|
35
|
+
<rect
|
|
36
|
+
x="0.75" y="0.75"
|
|
37
|
+
rx={radius} ry={radius}
|
|
38
|
+
fill="none"
|
|
39
|
+
stroke="var(--color-border-default)"
|
|
40
|
+
strokeWidth="1.5"
|
|
41
|
+
strokeDasharray="6 6"
|
|
42
|
+
style={{ width: "calc(100% - 1.5px)", height: "calc(100% - 1.5px)" }}
|
|
43
|
+
/>
|
|
44
|
+
</svg>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A node card for the diagram family (FlowDiagram, alias resolve, …). Raised
|
|
50
|
+
* two-layer treatment: a 4px stroke ring underneath + a surface on top whose
|
|
51
|
+
* lift shadow falls OVER the stroke (button-like). The stroke grows 0→4px as
|
|
52
|
+
* the node fills (smooth, no layout shift). Empty nodes render a dashed slot.
|
|
53
|
+
*
|
|
54
|
+
* Node type is encoded by color: wallet = white/raised, anchor = inverted slate,
|
|
55
|
+
* accent = pair-colored state (issue/destroy).
|
|
56
|
+
*/
|
|
57
|
+
function DiagramNode({
|
|
58
|
+
variant = "wallet",
|
|
59
|
+
filled = true,
|
|
60
|
+
accent,
|
|
61
|
+
accentInverted = false,
|
|
62
|
+
compact = false,
|
|
63
|
+
className,
|
|
64
|
+
children,
|
|
65
|
+
}: DiagramNodeProps) {
|
|
66
|
+
const sizeClass = compact ? "px-4 py-2 min-h-[48px] min-w-[140px]" : "px-5 py-3 min-h-[64px] min-w-[180px]"
|
|
67
|
+
|
|
68
|
+
// resolve fill / stroke / text per type
|
|
69
|
+
let fill = "var(--color-bg-raised)"
|
|
70
|
+
let stroke = "var(--color-border-default)"
|
|
71
|
+
let ink: string | undefined
|
|
72
|
+
|
|
73
|
+
if (accent) {
|
|
74
|
+
fill = accentInverted ? `var(--color-pair-${accent}-light)` : `var(--color-pair-${accent}-dark)`
|
|
75
|
+
stroke = accentInverted ? `var(--color-pair-${accent}-dark)` : `var(--color-pair-${accent}-light)`
|
|
76
|
+
ink = accentInverted ? "var(--color-text-default)" : "var(--color-text-inverse)"
|
|
77
|
+
} else if (variant === "anchor") {
|
|
78
|
+
fill = "var(--color-pair-blue-navy-dark)"
|
|
79
|
+
stroke = "var(--color-pair-blue-navy-light)"
|
|
80
|
+
ink = "var(--color-text-inverse)"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
data-slot="diagram-node"
|
|
86
|
+
className={cn("relative [border-radius:var(--radius-card)]", className)}
|
|
87
|
+
style={filled ? { animation: "diagram-node-pop .18s cubic-bezier(0.16,1,0.3,1) both" } : undefined}
|
|
88
|
+
>
|
|
89
|
+
<style>{`@keyframes diagram-node-pop { from { opacity:0; transform: scale(.96) } to { opacity:1; transform: scale(1) } }`}</style>
|
|
90
|
+
|
|
91
|
+
{/* bottom: stroke ring that grows 0→4px on fill */}
|
|
92
|
+
<div
|
|
93
|
+
className="absolute inset-0 [border-radius:var(--radius-card)] transition-[box-shadow] duration-300 ease-out"
|
|
94
|
+
style={{ boxShadow: filled ? `inset 0 0 0 4px ${stroke}` : "inset 0 0 0 0px transparent" }}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{/* top: surface — inset so the ring shows; bg + lift fade in on fill */}
|
|
98
|
+
<div
|
|
99
|
+
className={cn(
|
|
100
|
+
"relative m-1 flex items-center justify-center [border-radius:calc(var(--radius-card)-4px)] text-center transition-[background-color,box-shadow] duration-300 ease-out",
|
|
101
|
+
sizeClass,
|
|
102
|
+
)}
|
|
103
|
+
style={{
|
|
104
|
+
backgroundColor: filled ? fill : "var(--color-bg-base)",
|
|
105
|
+
color: filled ? ink : undefined,
|
|
106
|
+
boxShadow: filled ? "var(--shadow-raised)" : "none",
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{!filled && <DashedBorder radius={6} />}
|
|
110
|
+
{children}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { DiagramNode }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
+
import { DiagramNode } from "./diagram-node"
|
|
4
5
|
|
|
5
6
|
/** Brand pair used to give a node / the flow a semantic accent. */
|
|
6
7
|
export type FlowAccent =
|
|
@@ -185,72 +186,66 @@ function Connector({
|
|
|
185
186
|
)
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
function FlowNodeCard({ node, active, format
|
|
189
|
+
function FlowNodeCard({ node, active, format }: { node: FlowNode; active: boolean; format: FormatFn; pop?: boolean }) {
|
|
189
190
|
const showAfter = active && node.after !== null
|
|
190
191
|
const overdrawn = node.after !== null && node.after < 0
|
|
191
192
|
|
|
193
|
+
// Empty slot
|
|
192
194
|
if (node.empty) {
|
|
193
195
|
return (
|
|
194
|
-
<
|
|
195
|
-
<DashedBorder radius={10} />
|
|
196
|
+
<DiagramNode filled={false}>
|
|
196
197
|
<span className="text-body-sm text-[var(--color-text-hint)]">{node.name}</span>
|
|
197
|
-
</
|
|
198
|
+
</DiagramNode>
|
|
198
199
|
)
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
// State node (issue/destroy
|
|
202
|
-
// inline next to the main label; subtext below.
|
|
202
|
+
// State node (issue/destroy): pair-colored, no balance. Icon + label, subtext below.
|
|
203
203
|
if (node.accent) {
|
|
204
|
-
// default: dark fill + light text; inverted: light fill + dark text.
|
|
205
|
-
const fill = node.accentInverted ? `var(--color-pair-${node.accent}-light)` : `var(--color-pair-${node.accent}-dark)`
|
|
206
|
-
const ink = node.accentInverted ? `var(--color-pair-${node.accent}-dark)` : `var(--color-pair-${node.accent}-light)`
|
|
207
|
-
const ring = node.accentInverted ? `var(--color-pair-${node.accent}-dark)` : `var(--color-pair-${node.accent}-light)`
|
|
208
204
|
const subInk = node.accentInverted ? "var(--color-text-default)" : "var(--color-text-inverse)"
|
|
209
205
|
return (
|
|
210
|
-
<
|
|
211
|
-
className="flex flex-col items-center gap-1
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{node.
|
|
217
|
-
</
|
|
218
|
-
|
|
219
|
-
</div>
|
|
206
|
+
<DiagramNode accent={node.accent} accentInverted={node.accentInverted}>
|
|
207
|
+
<div className="flex flex-col items-center gap-1">
|
|
208
|
+
<span className="flex items-center gap-1.5 text-body-sm">
|
|
209
|
+
{node.icon && <span className="flex items-center [&_svg]:size-4">{node.icon}</span>}
|
|
210
|
+
{node.name}
|
|
211
|
+
</span>
|
|
212
|
+
{node.subtitle && <span className="text-caption" style={{ color: subInk }}>{node.subtitle}</span>}
|
|
213
|
+
</div>
|
|
214
|
+
</DiagramNode>
|
|
220
215
|
)
|
|
221
216
|
}
|
|
222
217
|
|
|
218
|
+
// Wallet / balance node
|
|
223
219
|
return (
|
|
224
|
-
<
|
|
225
|
-
className="flex flex-col items-center gap-1
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
) : (
|
|
233
|
-
<div className="flex flex-col items-center leading-none">
|
|
234
|
-
<span
|
|
235
|
-
className={`origin-bottom transition-all duration-300 ${
|
|
236
|
-
showAfter
|
|
237
|
-
? "scale-[0.78] text-caption text-[var(--color-text-muted)] mb-0.5"
|
|
238
|
-
: "text-label-mono text-[var(--color-text-default)]"
|
|
239
|
-
}`}
|
|
240
|
-
>
|
|
241
|
-
{showAfter ? `was ${format(node.current)}` : format(node.current)}
|
|
242
|
-
</span>
|
|
243
|
-
{showAfter && (
|
|
220
|
+
<DiagramNode variant="wallet">
|
|
221
|
+
<div className="flex flex-col items-center gap-1">
|
|
222
|
+
<span className="text-body-sm text-[var(--color-text-default)]">{node.name}</span>
|
|
223
|
+
|
|
224
|
+
{node.current === null ? (
|
|
225
|
+
node.subtitle ? <span className="text-caption text-[var(--color-text-muted)]">{node.subtitle}</span> : null
|
|
226
|
+
) : (
|
|
227
|
+
<div className="flex flex-col items-center leading-none">
|
|
244
228
|
<span
|
|
245
|
-
className={`
|
|
246
|
-
|
|
229
|
+
className={`origin-bottom transition-all duration-300 ${
|
|
230
|
+
showAfter
|
|
231
|
+
? "scale-[0.78] text-caption text-[var(--color-text-muted)] mb-0.5"
|
|
232
|
+
: "text-label-mono text-[var(--color-text-default)]"
|
|
233
|
+
}`}
|
|
247
234
|
>
|
|
248
|
-
{format(node.
|
|
235
|
+
{showAfter ? `was ${format(node.current)}` : format(node.current)}
|
|
249
236
|
</span>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
237
|
+
{showAfter && (
|
|
238
|
+
<span
|
|
239
|
+
className={`text-label-mono ${overdrawn ? "text-[var(--color-feedback-error)]" : "text-[var(--color-text-default)]"}`}
|
|
240
|
+
style={{ animation: "flow-slide-in .3s ease both" }}
|
|
241
|
+
>
|
|
242
|
+
{format(node.after!)}
|
|
243
|
+
</span>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
</DiagramNode>
|
|
254
249
|
)
|
|
255
250
|
}
|
|
256
251
|
|
package/src/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ export * from "./components/ui/tabs"
|
|
|
44
44
|
export * from "./components/ui/textarea"
|
|
45
45
|
export * from "./components/ui/tooltip"
|
|
46
46
|
export * from "./components/ui/flow-diagram"
|
|
47
|
+
export * from "./components/ui/diagram-node"
|
|
47
48
|
export * from "./components/ui/help-expander"
|
|
48
49
|
|
|
49
50
|
// Brand textures + logo
|
package/tokens/primitives.css
CHANGED
|
@@ -240,6 +240,7 @@
|
|
|
240
240
|
--primitive-shadow-md: 0 4px 6px oklch(0 0 0 / 0.07), 0 2px 4px oklch(0 0 0 / 0.06);
|
|
241
241
|
--primitive-shadow-lg: 0 10px 15px oklch(0 0 0 / 0.10), 0 4px 6px oklch(0 0 0 / 0.05);
|
|
242
242
|
--primitive-shadow-xl: 0 20px 25px oklch(0 0 0 / 0.10), 0 8px 10px oklch(0 0 0 / 0.04);
|
|
243
|
+
--primitive-shadow-raised: 0 4px 12px -1px oklch(0 0 0 / 0.18), 0 2px 5px -1px oklch(0 0 0 / 0.12);
|
|
243
244
|
|
|
244
245
|
/* --- Z-index scale --- */
|
|
245
246
|
--primitive-z-base: 0;
|
package/tokens/semantic.css
CHANGED
|
@@ -192,6 +192,7 @@
|
|
|
192
192
|
--shadow-card: var(--primitive-shadow-xs);
|
|
193
193
|
--shadow-popover: var(--primitive-shadow-md);
|
|
194
194
|
--shadow-modal: var(--primitive-shadow-lg);
|
|
195
|
+
--shadow-raised: var(--primitive-shadow-raised);
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
/* ── Dark mode ───────────────────────────────────────────────────── */
|
|
@@ -321,4 +322,5 @@
|
|
|
321
322
|
--shadow-card: none;
|
|
322
323
|
--shadow-popover: var(--primitive-shadow-lg);
|
|
323
324
|
--shadow-modal: var(--primitive-shadow-xl);
|
|
325
|
+
--shadow-raised: var(--primitive-shadow-raised);
|
|
324
326
|
}
|