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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minka-ds",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Minka product design system — tokenized component library",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -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, pop }: { node: FlowNode; active: boolean; format: FormatFn; pop?: boolean }) {
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
- <div className="relative flex flex-col items-center justify-center gap-1 [border-radius:var(--radius-card)] bg-[var(--color-bg-base)] px-5 py-3 text-center min-w-[180px] min-h-[64px]">
195
- <DashedBorder radius={10} />
196
+ <DiagramNode filled={false}>
196
197
  <span className="text-body-sm text-[var(--color-text-hint)]">{node.name}</span>
197
- </div>
198
+ </DiagramNode>
198
199
  )
199
200
  }
200
201
 
201
- // State node (issue/destroy abstract end): pair-colored, no balance. Icon sits
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
- <div
211
- className="flex flex-col items-center gap-1 [border-radius:var(--radius-card)] border px-5 py-3 text-center min-w-[180px]"
212
- style={{ backgroundColor: fill, color: ink, borderColor: ring }}
213
- >
214
- <span className="flex items-center gap-1.5 text-body-sm">
215
- {node.icon && <span className="flex items-center [&_svg]:size-4">{node.icon}</span>}
216
- {node.name}
217
- </span>
218
- {node.subtitle && <span className="text-caption" style={{ color: subInk }}>{node.subtitle}</span>}
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
- <div
225
- className="flex flex-col items-center gap-1 [border-radius:var(--radius-card)] border border-[var(--color-border-default)] bg-[var(--color-bg-raised)] px-5 py-3 text-center min-w-[180px]"
226
- style={pop ? { animation: "flow-pop .18s cubic-bezier(0.16,1,0.3,1) both" } : undefined}
227
- >
228
- <span className="text-body-sm text-[var(--color-text-default)]">{node.name}</span>
229
-
230
- {node.current === null ? (
231
- node.subtitle ? <span className="text-caption text-[var(--color-text-muted)]">{node.subtitle}</span> : null
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={`text-label-mono ${overdrawn ? "text-[var(--color-feedback-error)]" : "text-[var(--color-text-default)]"}`}
246
- style={{ animation: "flow-slide-in .3s ease both" }}
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.after!)}
235
+ {showAfter ? `was ${format(node.current)}` : format(node.current)}
249
236
  </span>
250
- )}
251
- </div>
252
- )}
253
- </div>
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
@@ -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;
@@ -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
  }