@duro-app/diagrams 0.31.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/component-meta.d.ts +15 -0
  3. package/dist/component-meta.d.ts.map +1 -0
  4. package/dist/components/Arrow/Arrow.d.ts +24 -0
  5. package/dist/components/Arrow/Arrow.d.ts.map +1 -0
  6. package/dist/components/Arrow/Arrow.meta.d.ts +3 -0
  7. package/dist/components/Arrow/Arrow.meta.d.ts.map +1 -0
  8. package/dist/components/Diagram/Diagram.d.ts +11 -0
  9. package/dist/components/Diagram/Diagram.d.ts.map +1 -0
  10. package/dist/components/Diagram/Diagram.meta.d.ts +3 -0
  11. package/dist/components/Diagram/Diagram.meta.d.ts.map +1 -0
  12. package/dist/components/Diagram/Diagram.stories.d.ts +33 -0
  13. package/dist/components/Diagram/Diagram.stories.d.ts.map +1 -0
  14. package/dist/components/Leader/Leader.d.ts +7 -0
  15. package/dist/components/Leader/Leader.d.ts.map +1 -0
  16. package/dist/components/Leader/Leader.meta.d.ts +3 -0
  17. package/dist/components/Leader/Leader.meta.d.ts.map +1 -0
  18. package/dist/components/Node/Node.d.ts +13 -0
  19. package/dist/components/Node/Node.d.ts.map +1 -0
  20. package/dist/components/Node/Node.meta.d.ts +3 -0
  21. package/dist/components/Node/Node.meta.d.ts.map +1 -0
  22. package/dist/components/Text/Text.d.ts +12 -0
  23. package/dist/components/Text/Text.d.ts.map +1 -0
  24. package/dist/components/Text/Text.meta.d.ts +3 -0
  25. package/dist/components/Text/Text.meta.d.ts.map +1 -0
  26. package/dist/components/types.d.ts +4 -0
  27. package/dist/components/types.d.ts.map +1 -0
  28. package/dist/index.css +1 -0
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +93 -0
  32. package/dist/index.js.map +1 -0
  33. package/package.json +56 -0
  34. package/src/component-meta.ts +8 -0
  35. package/src/components/Arrow/Arrow.meta.ts +16 -0
  36. package/src/components/Arrow/Arrow.tsx +48 -0
  37. package/src/components/Diagram/Diagram.meta.ts +21 -0
  38. package/src/components/Diagram/Diagram.stories.tsx +137 -0
  39. package/src/components/Diagram/Diagram.tsx +47 -0
  40. package/src/components/Leader/Leader.meta.ts +13 -0
  41. package/src/components/Leader/Leader.tsx +12 -0
  42. package/src/components/Node/Node.meta.ts +21 -0
  43. package/src/components/Node/Node.tsx +32 -0
  44. package/src/components/Text/Text.meta.ts +14 -0
  45. package/src/components/Text/Text.tsx +20 -0
  46. package/src/components/types.ts +24 -0
  47. package/src/index.ts +8 -0
  48. package/src/styles.css +359 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frederic Rousseau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,15 @@
1
+ export interface ComponentMeta {
2
+ description: string;
3
+ whenToUse: string[];
4
+ whenNotToUse: string[];
5
+ anatomy?: {
6
+ required: string[];
7
+ optional?: string[];
8
+ };
9
+ relatedTo?: Array<{
10
+ component: string;
11
+ relationship: string;
12
+ }>;
13
+ example: string;
14
+ }
15
+ //# sourceMappingURL=component-meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-meta.d.ts","sourceRoot":"","sources":["../src/component-meta.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,CAAC,EAAE;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAC,CAAA;IACnD,SAAS,CAAC,EAAE,KAAK,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;IAC5D,OAAO,EAAE,MAAM,CAAA;CAChB"}
@@ -0,0 +1,24 @@
1
+ import { Point } from '../types';
2
+ export interface ArrowProps {
3
+ from: Point;
4
+ to: Point;
5
+ /**
6
+ * `none` (default) draws a straight line.
7
+ * `L` draws a 2-segment bend: horizontal-then-vertical or vertical-then-horizontal.
8
+ * `Z` draws a 3-segment dogleg so the middle leg is detached from both endpoints —
9
+ * useful for branching/converging arrows that should not run along the source or
10
+ * target box's edge.
11
+ * `axis` controls the orientation of the middle leg.
12
+ * - axis='v' (default): leg order is V-H or V-H-V (middle leg horizontal).
13
+ * - axis='h': leg order is H-V or H-V-H (middle leg vertical).
14
+ */
15
+ bend?: 'none' | 'L' | 'Z';
16
+ axis?: 'v' | 'h';
17
+ /**
18
+ * For `Z` bends, the position of the middle leg as a fraction (0..1) between
19
+ * `from` and `to` along the primary axis. Defaults to 0.5 (centered).
20
+ */
21
+ mid?: number;
22
+ }
23
+ export declare function Arrow({ from, to, bend, axis, mid }: ArrowProps): import("react/jsx-runtime").JSX.Element;
24
+ //# sourceMappingURL=Arrow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Arrow.d.ts","sourceRoot":"","sources":["../../../src/components/Arrow/Arrow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,UAAU,CAAA;AAEnC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,CAAA;IACX,EAAE,EAAE,KAAK,CAAA;IACT;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG,CAAA;IACzB,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,CAAA;IAChB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,KAAK,CAAC,EAAC,IAAI,EAAE,EAAE,EAAE,IAAa,EAAE,IAAU,EAAE,GAAS,EAAC,EAAE,UAAU,2CAuBjF"}
@@ -0,0 +1,3 @@
1
+ import { ComponentMeta } from '../../component-meta';
2
+ export declare const meta: ComponentMeta;
3
+ //# sourceMappingURL=Arrow.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Arrow.meta.d.ts","sourceRoot":"","sources":["../../../src/components/Arrow/Arrow.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAA;AAEvD,eAAO,MAAM,IAAI,EAAE,aAalB,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from 'react';
2
+ export interface DiagramProps {
3
+ width: number;
4
+ height: number;
5
+ title: string;
6
+ desc?: string;
7
+ viewBox?: string;
8
+ children: ReactNode;
9
+ }
10
+ export declare function Diagram({ width, height, title, desc, viewBox, children }: DiagramProps): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=Diagram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Diagram.d.ts","sourceRoot":"","sources":["../../../src/components/Diagram/Diagram.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAA;AAEpC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,OAAO,CAAC,EAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAC,EAAE,YAAY,2CAmCpF"}
@@ -0,0 +1,3 @@
1
+ import { ComponentMeta } from '../../component-meta';
2
+ export declare const meta: ComponentMeta;
3
+ //# sourceMappingURL=Diagram.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Diagram.meta.d.ts","sourceRoot":"","sources":["../../../src/components/Diagram/Diagram.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAA;AAEvD,eAAO,MAAM,IAAI,EAAE,aAkBlB,CAAA"}
@@ -0,0 +1,33 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Diagram } from './Diagram';
3
+ declare const meta: Meta<typeof Diagram>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Diagram>;
6
+ /**
7
+ * One swatch row per ramp. Each row shows a Node with both title and subtitle
8
+ * so all four colour stops (fill, stroke, title, subtitle) are exercised. Wrap
9
+ * the story in a different ThemeProvider via the toolbar background switcher
10
+ * to verify dark / light / high-contrast all flip cleanly.
11
+ */
12
+ export declare const RampSwatches: Story;
13
+ /**
14
+ * The chat's worked example: an idempotent Vault PKI certificate-issuance flow.
15
+ * Exercises Diagram + Node + Arrow (straight + L-bend) + Leader + Text in one piece.
16
+ *
17
+ * Layout, with the box centres marked:
18
+ *
19
+ * Start (240,20)–(440,76)
20
+ * |
21
+ * Lookup metadata (240,120)–(440,192)
22
+ * |
23
+ * decision split (no diamond — colour split + L-bends carry meaning)
24
+ * / \
25
+ * Cached? Y N Cached?
26
+ * | |
27
+ * Return cached cert Issue new certificate (then back to bottom row)
28
+ * (40,240)–(280,312) (400,240)–(640,312)
29
+ * \ /
30
+ * End (240,360)–(440,416)
31
+ */
32
+ export declare const VaultPkiFlow: Story;
33
+ //# sourceMappingURL=Diagram.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Diagram.stories.d.ts","sourceRoot":"","sources":["../../../src/components/Diagram/Diagram.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAEpD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAOjC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,OAAO,CAG9B,CAAA;AACD,eAAe,IAAI,CAAA;AACnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,OAAO,CAAC,CAAA;AAErC;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,KA2B1B,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,EAAE,KAkE1B,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { Point } from '../types';
2
+ export interface LeaderProps {
3
+ from: Point;
4
+ to: Point;
5
+ }
6
+ export declare function Leader({ from, to }: LeaderProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=Leader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Leader.d.ts","sourceRoot":"","sources":["../../../src/components/Leader/Leader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,UAAU,CAAA;AAEnC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,KAAK,CAAA;IACX,EAAE,EAAE,KAAK,CAAA;CACV;AAED,wBAAgB,MAAM,CAAC,EAAC,IAAI,EAAE,EAAE,EAAC,EAAE,WAAW,2CAI7C"}
@@ -0,0 +1,3 @@
1
+ import { ComponentMeta } from '../../component-meta';
2
+ export declare const meta: ComponentMeta;
3
+ //# sourceMappingURL=Leader.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Leader.meta.d.ts","sourceRoot":"","sources":["../../../src/components/Leader/Leader.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAA;AAEvD,eAAO,MAAM,IAAI,EAAE,aAUlB,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { Ramp } from '../types';
2
+ export interface NodeProps {
3
+ x: number;
4
+ y: number;
5
+ w: number;
6
+ h: number;
7
+ ramp: Ramp;
8
+ title: string;
9
+ subtitle?: string;
10
+ rx?: number;
11
+ }
12
+ export declare function Node({ x, y, w, h, ramp, title, subtitle, rx }: NodeProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=Node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Node.d.ts","sourceRoot":"","sources":["../../../src/components/Node/Node.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,UAAU,CAAA;AAElC,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,IAAI,EAAE,IAAI,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,wBAAgB,IAAI,CAAC,EAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAM,EAAC,EAAE,SAAS,2CAkB1E"}
@@ -0,0 +1,3 @@
1
+ import { ComponentMeta } from '../../component-meta';
2
+ export declare const meta: ComponentMeta;
3
+ //# sourceMappingURL=Node.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Node.meta.d.ts","sourceRoot":"","sources":["../../../src/components/Node/Node.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAA;AAEvD,eAAO,MAAM,IAAI,EAAE,aAkBlB,CAAA"}
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from 'react';
2
+ export type TextVariant = 't' | 'ts' | 'th';
3
+ export type TextAnchor = 'start' | 'middle' | 'end';
4
+ export interface TextProps {
5
+ x: number;
6
+ y: number;
7
+ variant?: TextVariant;
8
+ anchor?: TextAnchor;
9
+ children: ReactNode;
10
+ }
11
+ export declare function Text({ x, y, variant, anchor, children }: TextProps): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=Text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Text.d.ts","sourceRoot":"","sources":["../../../src/components/Text/Text.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAA;AAEpC,MAAM,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAA;AAC3C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEnD,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,IAAI,CAAC,EAAC,CAAC,EAAE,CAAC,EAAE,OAAa,EAAE,MAAgB,EAAE,QAAQ,EAAC,EAAE,SAAS,2CAMhF"}
@@ -0,0 +1,3 @@
1
+ import { ComponentMeta } from '../../component-meta';
2
+ export declare const meta: ComponentMeta;
3
+ //# sourceMappingURL=Text.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Text.meta.d.ts","sourceRoot":"","sources":["../../../src/components/Text/Text.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAA;AAEvD,eAAO,MAAM,IAAI,EAAE,aAWlB,CAAA"}
@@ -0,0 +1,4 @@
1
+ export type Ramp = 'purple' | 'teal' | 'coral' | 'pink' | 'gray' | 'blue' | 'green' | 'amber' | 'red';
2
+ export declare const RAMPS: readonly Ramp[];
3
+ export type Point = readonly [number, number];
4
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/components/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,IAAI,GACZ,QAAQ,GACR,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,MAAM,GACN,OAAO,GACP,OAAO,GACP,KAAK,CAAA;AAET,eAAO,MAAM,KAAK,EAAE,SAAS,IAAI,EAUvB,CAAA;AAEV,MAAM,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA"}
package/dist/index.css ADDED
@@ -0,0 +1 @@
1
+ :where(:root,[data-theme=dark]){--duro-diagram-purple-fill: #5b21b6;--duro-diagram-purple-stroke: #ddd6fe;--duro-diagram-purple-title: #ede9fe;--duro-diagram-purple-subtitle: #ddd6fe;--duro-diagram-teal-fill: #115e59;--duro-diagram-teal-stroke: #99f6e4;--duro-diagram-teal-title: #ccfbf1;--duro-diagram-teal-subtitle: #99f6e4;--duro-diagram-coral-fill: #9f1239;--duro-diagram-coral-stroke: #fecdd3;--duro-diagram-coral-title: #ffe4e6;--duro-diagram-coral-subtitle: #fecdd3;--duro-diagram-pink-fill: #9d174d;--duro-diagram-pink-stroke: #fbcfe8;--duro-diagram-pink-title: #fce7f3;--duro-diagram-pink-subtitle: #fbcfe8;--duro-diagram-gray-fill: #262626;--duro-diagram-gray-stroke: #d4d4d4;--duro-diagram-gray-title: #e5e5e5;--duro-diagram-gray-subtitle: #d4d4d4;--duro-diagram-blue-fill: #1e3a8a;--duro-diagram-blue-stroke: #bfdbfe;--duro-diagram-blue-title: #dbeafe;--duro-diagram-blue-subtitle: #bfdbfe;--duro-diagram-green-fill: #166534;--duro-diagram-green-stroke: #bbf7d0;--duro-diagram-green-title: #dcfce7;--duro-diagram-green-subtitle: #bbf7d0;--duro-diagram-amber-fill: #92400e;--duro-diagram-amber-stroke: #fde68a;--duro-diagram-amber-title: #fef3c7;--duro-diagram-amber-subtitle: #fde68a;--duro-diagram-red-fill: #991b1b;--duro-diagram-red-stroke: #fecaca;--duro-diagram-red-title: #fee2e2;--duro-diagram-red-subtitle: #fecaca;--duro-diagram-arrow-stroke: #6b6b6b;--duro-diagram-leader-stroke: #4a4a4a;--duro-diagram-box-fill: transparent;--duro-diagram-box-stroke: #333333;--duro-diagram-text: #e5e5e5;--duro-diagram-text-muted: #b0b0b0}[data-theme=light]{--duro-diagram-purple-fill: #f5f3ff;--duro-diagram-purple-stroke: #7c3aed;--duro-diagram-purple-title: #5b21b6;--duro-diagram-purple-subtitle: #7c3aed;--duro-diagram-teal-fill: #f0fdfa;--duro-diagram-teal-stroke: #0d9488;--duro-diagram-teal-title: #115e59;--duro-diagram-teal-subtitle: #0d9488;--duro-diagram-coral-fill: #fff1f2;--duro-diagram-coral-stroke: #e11d48;--duro-diagram-coral-title: #9f1239;--duro-diagram-coral-subtitle: #e11d48;--duro-diagram-pink-fill: #fdf2f8;--duro-diagram-pink-stroke: #db2777;--duro-diagram-pink-title: #9d174d;--duro-diagram-pink-subtitle: #db2777;--duro-diagram-gray-fill: #f5f5f5;--duro-diagram-gray-stroke: #525252;--duro-diagram-gray-title: #262626;--duro-diagram-gray-subtitle: #525252;--duro-diagram-blue-fill: #eff6ff;--duro-diagram-blue-stroke: #2563eb;--duro-diagram-blue-title: #1e3a8a;--duro-diagram-blue-subtitle: #2563eb;--duro-diagram-green-fill: #f0fdf4;--duro-diagram-green-stroke: #16a34a;--duro-diagram-green-title: #166534;--duro-diagram-green-subtitle: #16a34a;--duro-diagram-amber-fill: #fffbeb;--duro-diagram-amber-stroke: #d97706;--duro-diagram-amber-title: #92400e;--duro-diagram-amber-subtitle: #d97706;--duro-diagram-red-fill: #fef2f2;--duro-diagram-red-stroke: #dc2626;--duro-diagram-red-title: #991b1b;--duro-diagram-red-subtitle: #dc2626;--duro-diagram-arrow-stroke: #888888;--duro-diagram-leader-stroke: #c0c0c0;--duro-diagram-box-fill: transparent;--duro-diagram-box-stroke: #d4d4d4;--duro-diagram-text: #1a1a1a;--duro-diagram-text-muted: #4a4a4a}[data-theme=high-contrast]{--duro-diagram-purple-fill: #4c1d95;--duro-diagram-purple-stroke: #ede9fe;--duro-diagram-purple-title: #f5f3ff;--duro-diagram-purple-subtitle: #ede9fe;--duro-diagram-teal-fill: #134e4a;--duro-diagram-teal-stroke: #ccfbf1;--duro-diagram-teal-title: #f0fdfa;--duro-diagram-teal-subtitle: #ccfbf1;--duro-diagram-coral-fill: #881337;--duro-diagram-coral-stroke: #ffe4e6;--duro-diagram-coral-title: #fff1f2;--duro-diagram-coral-subtitle: #ffe4e6;--duro-diagram-pink-fill: #831843;--duro-diagram-pink-stroke: #fce7f3;--duro-diagram-pink-title: #fdf2f8;--duro-diagram-pink-subtitle: #fce7f3;--duro-diagram-gray-fill: #171717;--duro-diagram-gray-stroke: #e5e5e5;--duro-diagram-gray-title: #f5f5f5;--duro-diagram-gray-subtitle: #e5e5e5;--duro-diagram-blue-fill: #1e1b4b;--duro-diagram-blue-stroke: #dbeafe;--duro-diagram-blue-title: #eff6ff;--duro-diagram-blue-subtitle: #dbeafe;--duro-diagram-green-fill: #14532d;--duro-diagram-green-stroke: #dcfce7;--duro-diagram-green-title: #f0fdf4;--duro-diagram-green-subtitle: #dcfce7;--duro-diagram-amber-fill: #78350f;--duro-diagram-amber-stroke: #fef3c7;--duro-diagram-amber-title: #fffbeb;--duro-diagram-amber-subtitle: #fef3c7;--duro-diagram-red-fill: #7f1d1d;--duro-diagram-red-stroke: #fee2e2;--duro-diagram-red-title: #fef2f2;--duro-diagram-red-subtitle: #fee2e2;--duro-diagram-arrow-stroke: #ffffff;--duro-diagram-leader-stroke: #888888;--duro-diagram-box-fill: transparent;--duro-diagram-box-stroke: #555555;--duro-diagram-text: #ffffff;--duro-diagram-text-muted: #b0b0b0}.duro-diagram .t{font:400 14px/1.4 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;fill:var(--duro-diagram-text);stroke:none}.duro-diagram .ts{font:400 12px/1.4 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;fill:var(--duro-diagram-text-muted);stroke:none}.duro-diagram .th{font:500 14px/1.4 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;fill:var(--duro-diagram-text);stroke:none}.duro-diagram .box{fill:var(--duro-diagram-box-fill);stroke:var(--duro-diagram-box-stroke);stroke-width:.5}.duro-diagram .arr{stroke:var(--duro-diagram-arrow-stroke);stroke-width:1.5;fill:none}.duro-diagram .leader{stroke:var(--duro-diagram-leader-stroke);stroke-width:.5;stroke-dasharray:3 3;fill:none}.duro-diagram .c-purple>rect{fill:var(--duro-diagram-purple-fill);stroke:var(--duro-diagram-purple-stroke)}.duro-diagram .c-purple>.th{fill:var(--duro-diagram-purple-title)}.duro-diagram .c-purple>.ts{fill:var(--duro-diagram-purple-subtitle)}.duro-diagram .c-teal>rect{fill:var(--duro-diagram-teal-fill);stroke:var(--duro-diagram-teal-stroke)}.duro-diagram .c-teal>.th{fill:var(--duro-diagram-teal-title)}.duro-diagram .c-teal>.ts{fill:var(--duro-diagram-teal-subtitle)}.duro-diagram .c-coral>rect{fill:var(--duro-diagram-coral-fill);stroke:var(--duro-diagram-coral-stroke)}.duro-diagram .c-coral>.th{fill:var(--duro-diagram-coral-title)}.duro-diagram .c-coral>.ts{fill:var(--duro-diagram-coral-subtitle)}.duro-diagram .c-pink>rect{fill:var(--duro-diagram-pink-fill);stroke:var(--duro-diagram-pink-stroke)}.duro-diagram .c-pink>.th{fill:var(--duro-diagram-pink-title)}.duro-diagram .c-pink>.ts{fill:var(--duro-diagram-pink-subtitle)}.duro-diagram .c-gray>rect{fill:var(--duro-diagram-gray-fill);stroke:var(--duro-diagram-gray-stroke)}.duro-diagram .c-gray>.th{fill:var(--duro-diagram-gray-title)}.duro-diagram .c-gray>.ts{fill:var(--duro-diagram-gray-subtitle)}.duro-diagram .c-blue>rect{fill:var(--duro-diagram-blue-fill);stroke:var(--duro-diagram-blue-stroke)}.duro-diagram .c-blue>.th{fill:var(--duro-diagram-blue-title)}.duro-diagram .c-blue>.ts{fill:var(--duro-diagram-blue-subtitle)}.duro-diagram .c-green>rect{fill:var(--duro-diagram-green-fill);stroke:var(--duro-diagram-green-stroke)}.duro-diagram .c-green>.th{fill:var(--duro-diagram-green-title)}.duro-diagram .c-green>.ts{fill:var(--duro-diagram-green-subtitle)}.duro-diagram .c-amber>rect{fill:var(--duro-diagram-amber-fill);stroke:var(--duro-diagram-amber-stroke)}.duro-diagram .c-amber>.th{fill:var(--duro-diagram-amber-title)}.duro-diagram .c-amber>.ts{fill:var(--duro-diagram-amber-subtitle)}.duro-diagram .c-red>rect{fill:var(--duro-diagram-red-fill);stroke:var(--duro-diagram-red-stroke)}.duro-diagram .c-red>.th{fill:var(--duro-diagram-red-title)}.duro-diagram .c-red>.ts{fill:var(--duro-diagram-red-subtitle)}
@@ -0,0 +1,7 @@
1
+ export { Diagram, type DiagramProps } from './components/Diagram/Diagram';
2
+ export { Node, type NodeProps } from './components/Node/Node';
3
+ export { Arrow, type ArrowProps } from './components/Arrow/Arrow';
4
+ export { Leader, type LeaderProps } from './components/Leader/Leader';
5
+ export { Text, type TextProps, type TextVariant, type TextAnchor } from './components/Text/Text';
6
+ export { RAMPS, type Ramp, type Point } from './components/types';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAA;AAErB,OAAO,EAAC,OAAO,EAAE,KAAK,YAAY,EAAC,MAAM,8BAA8B,CAAA;AACvE,OAAO,EAAC,IAAI,EAAE,KAAK,SAAS,EAAC,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAC,KAAK,EAAE,KAAK,UAAU,EAAC,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAC,MAAM,EAAE,KAAK,WAAW,EAAC,MAAM,4BAA4B,CAAA;AACnE,OAAO,EAAC,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAC,MAAM,wBAAwB,CAAA;AAC9F,OAAO,EAAC,KAAK,EAAE,KAAK,IAAI,EAAE,KAAK,KAAK,EAAC,MAAM,oBAAoB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,93 @@
1
+ import { jsxs as u, jsx as n } from "react/jsx-runtime";
2
+ function $({ width: o, height: t, title: a, desc: e, viewBox: l, children: r }) {
3
+ return /* @__PURE__ */ u(
4
+ "svg",
5
+ {
6
+ role: "img",
7
+ className: "duro-diagram",
8
+ viewBox: l ?? `0 0 ${o} ${t}`,
9
+ width: "100%",
10
+ style: { maxWidth: o, height: "auto", display: "block" },
11
+ xmlns: "http://www.w3.org/2000/svg",
12
+ children: [
13
+ /* @__PURE__ */ n("title", { children: a }),
14
+ e ? /* @__PURE__ */ n("desc", { children: e }) : null,
15
+ /* @__PURE__ */ n("defs", { children: /* @__PURE__ */ n(
16
+ "marker",
17
+ {
18
+ id: "duro-arrow",
19
+ viewBox: "0 0 10 10",
20
+ refX: 8,
21
+ refY: 5,
22
+ markerWidth: 6,
23
+ markerHeight: 6,
24
+ orient: "auto-start-reverse",
25
+ children: /* @__PURE__ */ n(
26
+ "path",
27
+ {
28
+ d: "M2 1L8 5L2 9",
29
+ fill: "none",
30
+ stroke: "context-stroke",
31
+ strokeWidth: 1.5,
32
+ strokeLinecap: "round",
33
+ strokeLinejoin: "round"
34
+ }
35
+ )
36
+ }
37
+ ) }),
38
+ r
39
+ ]
40
+ }
41
+ );
42
+ }
43
+ function x({ x: o, y: t, w: a, h: e, ramp: l, title: r, subtitle: i, rx: c = 8 }) {
44
+ const s = o + a / 2, d = i ? t + e / 2 - 9 : t + e / 2, m = t + e / 2 + 9;
45
+ return /* @__PURE__ */ u("g", { className: `node c-${l}`, children: [
46
+ /* @__PURE__ */ n("rect", { x: o, y: t, width: a, height: e, rx: c, strokeWidth: 0.5 }),
47
+ /* @__PURE__ */ n("text", { className: "th", x: s, y: d, textAnchor: "middle", dominantBaseline: "central", children: r }),
48
+ i ? /* @__PURE__ */ n("text", { className: "ts", x: s, y: m, textAnchor: "middle", dominantBaseline: "central", children: i }) : null
49
+ ] });
50
+ }
51
+ function f({ from: o, to: t, bend: a = "none", axis: e = "v", mid: l = 0.5 }) {
52
+ const [r, i] = o, [c, s] = t;
53
+ if (a === "none")
54
+ return /* @__PURE__ */ n("line", { className: "arr", x1: r, y1: i, x2: c, y2: s, markerEnd: "url(#duro-arrow)" });
55
+ let d;
56
+ if (a === "L")
57
+ d = e === "v" ? `M ${r} ${i} V ${s} H ${c}` : `M ${r} ${i} H ${c} V ${s}`;
58
+ else if (e === "v") {
59
+ const m = i + (s - i) * l;
60
+ d = `M ${r} ${i} V ${m} H ${c} V ${s}`;
61
+ } else {
62
+ const m = r + (c - r) * l;
63
+ d = `M ${r} ${i} H ${m} V ${s} H ${c}`;
64
+ }
65
+ return /* @__PURE__ */ n("path", { className: "arr", d, fill: "none", markerEnd: "url(#duro-arrow)" });
66
+ }
67
+ function k({ from: o, to: t }) {
68
+ const [a, e] = o, [l, r] = t;
69
+ return /* @__PURE__ */ n("line", { className: "leader", x1: a, y1: e, x2: l, y2: r });
70
+ }
71
+ function g({ x: o, y: t, variant: a = "t", anchor: e = "start", children: l }) {
72
+ return /* @__PURE__ */ n("text", { className: a, x: o, y: t, textAnchor: e, dominantBaseline: "central", children: l });
73
+ }
74
+ const p = [
75
+ "purple",
76
+ "teal",
77
+ "coral",
78
+ "pink",
79
+ "gray",
80
+ "blue",
81
+ "green",
82
+ "amber",
83
+ "red"
84
+ ];
85
+ export {
86
+ f as Arrow,
87
+ $ as Diagram,
88
+ k as Leader,
89
+ x as Node,
90
+ p as RAMPS,
91
+ g as Text
92
+ };
93
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/components/Diagram/Diagram.tsx","../src/components/Node/Node.tsx","../src/components/Arrow/Arrow.tsx","../src/components/Leader/Leader.tsx","../src/components/Text/Text.tsx","../src/components/types.ts"],"sourcesContent":["import type {ReactNode} from 'react'\n\nexport interface DiagramProps {\n width: number\n height: number\n title: string\n desc?: string\n viewBox?: string\n children: ReactNode\n}\n\nexport function Diagram({width, height, title, desc, viewBox, children}: DiagramProps) {\n return (\n <svg\n role=\"img\"\n className=\"duro-diagram\"\n viewBox={viewBox ?? `0 0 ${width} ${height}`}\n width=\"100%\"\n style={{maxWidth: width, height: 'auto', display: 'block'}}\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <title>{title}</title>\n {desc ? <desc>{desc}</desc> : null}\n <defs>\n <marker\n id=\"duro-arrow\"\n viewBox=\"0 0 10 10\"\n refX={8}\n refY={5}\n markerWidth={6}\n markerHeight={6}\n orient=\"auto-start-reverse\"\n >\n <path\n d=\"M2 1L8 5L2 9\"\n fill=\"none\"\n stroke=\"context-stroke\"\n strokeWidth={1.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </marker>\n </defs>\n {children}\n </svg>\n )\n}\n","import type {Ramp} from '../types'\n\nexport interface NodeProps {\n x: number\n y: number\n w: number\n h: number\n ramp: Ramp\n title: string\n subtitle?: string\n rx?: number\n}\n\nexport function Node({x, y, w, h, ramp, title, subtitle, rx = 8}: NodeProps) {\n const cx = x + w / 2\n const titleY = subtitle ? y + h / 2 - 9 : y + h / 2\n const subtitleY = y + h / 2 + 9\n\n return (\n <g className={`node c-${ramp}`}>\n <rect x={x} y={y} width={w} height={h} rx={rx} strokeWidth={0.5} />\n <text className=\"th\" x={cx} y={titleY} textAnchor=\"middle\" dominantBaseline=\"central\">\n {title}\n </text>\n {subtitle ? (\n <text className=\"ts\" x={cx} y={subtitleY} textAnchor=\"middle\" dominantBaseline=\"central\">\n {subtitle}\n </text>\n ) : null}\n </g>\n )\n}\n","import type {Point} from '../types'\n\nexport interface ArrowProps {\n from: Point\n to: Point\n /**\n * `none` (default) draws a straight line.\n * `L` draws a 2-segment bend: horizontal-then-vertical or vertical-then-horizontal.\n * `Z` draws a 3-segment dogleg so the middle leg is detached from both endpoints —\n * useful for branching/converging arrows that should not run along the source or\n * target box's edge.\n * `axis` controls the orientation of the middle leg.\n * - axis='v' (default): leg order is V-H or V-H-V (middle leg horizontal).\n * - axis='h': leg order is H-V or H-V-H (middle leg vertical).\n */\n bend?: 'none' | 'L' | 'Z'\n axis?: 'v' | 'h'\n /**\n * For `Z` bends, the position of the middle leg as a fraction (0..1) between\n * `from` and `to` along the primary axis. Defaults to 0.5 (centered).\n */\n mid?: number\n}\n\nexport function Arrow({from, to, bend = 'none', axis = 'v', mid = 0.5}: ArrowProps) {\n const [x1, y1] = from\n const [x2, y2] = to\n\n if (bend === 'none') {\n return <line className=\"arr\" x1={x1} y1={y1} x2={x2} y2={y2} markerEnd=\"url(#duro-arrow)\" />\n }\n\n let d: string\n if (bend === 'L') {\n d = axis === 'v' ? `M ${x1} ${y1} V ${y2} H ${x2}` : `M ${x1} ${y1} H ${x2} V ${y2}`\n } else {\n // Z-bend: 3 segments with the middle leg detached from both endpoints.\n if (axis === 'v') {\n const midY = y1 + (y2 - y1) * mid\n d = `M ${x1} ${y1} V ${midY} H ${x2} V ${y2}`\n } else {\n const midX = x1 + (x2 - x1) * mid\n d = `M ${x1} ${y1} H ${midX} V ${y2} H ${x2}`\n }\n }\n\n return <path className=\"arr\" d={d} fill=\"none\" markerEnd=\"url(#duro-arrow)\" />\n}\n","import type {Point} from '../types'\n\nexport interface LeaderProps {\n from: Point\n to: Point\n}\n\nexport function Leader({from, to}: LeaderProps) {\n const [x1, y1] = from\n const [x2, y2] = to\n return <line className=\"leader\" x1={x1} y1={y1} x2={x2} y2={y2} />\n}\n","import type {ReactNode} from 'react'\n\nexport type TextVariant = 't' | 'ts' | 'th'\nexport type TextAnchor = 'start' | 'middle' | 'end'\n\nexport interface TextProps {\n x: number\n y: number\n variant?: TextVariant\n anchor?: TextAnchor\n children: ReactNode\n}\n\nexport function Text({x, y, variant = 't', anchor = 'start', children}: TextProps) {\n return (\n <text className={variant} x={x} y={y} textAnchor={anchor} dominantBaseline=\"central\">\n {children}\n </text>\n )\n}\n","export type Ramp =\n | 'purple'\n | 'teal'\n | 'coral'\n | 'pink'\n | 'gray'\n | 'blue'\n | 'green'\n | 'amber'\n | 'red'\n\nexport const RAMPS: readonly Ramp[] = [\n 'purple',\n 'teal',\n 'coral',\n 'pink',\n 'gray',\n 'blue',\n 'green',\n 'amber',\n 'red',\n] as const\n\nexport type Point = readonly [number, number]\n"],"names":["Diagram","width","height","title","desc","viewBox","children","jsxs","jsx","Node","x","y","w","h","ramp","subtitle","rx","cx","titleY","subtitleY","Arrow","from","to","bend","axis","mid","x1","y1","x2","y2","midY","midX","Leader","Text","variant","anchor","RAMPS"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@duro-app/diagrams",
3
+ "version": "0.31.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/fredericrous/duro-design-system",
9
+ "directory": "packages/diagrams"
10
+ },
11
+ "homepage": "https://github.com/fredericrous/duro-design-system",
12
+ "bugs": {
13
+ "url": "https://github.com/fredericrous/duro-design-system/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
22
+ "main": "dist/index.js",
23
+ "module": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "source": "./src/index.ts",
28
+ "import": "./dist/index.js",
29
+ "types": "./dist/index.d.ts"
30
+ },
31
+ "./dist/index.css": "./dist/index.css",
32
+ "./styles.css": "./dist/index.css"
33
+ },
34
+ "peerDependencies": {
35
+ "react": "^19.0.0",
36
+ "react-dom": "^19.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@babel/preset-typescript": "^7.28.0",
40
+ "@types/react": "^19.0.0",
41
+ "@types/react-dom": "^19.0.0",
42
+ "@vitejs/plugin-react": "^4.0.0",
43
+ "typescript": "^5.7.0",
44
+ "vite": "^6.0.0",
45
+ "vite-plugin-babel": "^1.5.0",
46
+ "vite-plugin-dts": "^4.0.0"
47
+ },
48
+ "sideEffects": [
49
+ "**/*.css"
50
+ ],
51
+ "scripts": {
52
+ "dev": "vite build --watch",
53
+ "build": "vite build",
54
+ "typecheck": "tsc --noEmit"
55
+ }
56
+ }
@@ -0,0 +1,8 @@
1
+ export interface ComponentMeta {
2
+ description: string
3
+ whenToUse: string[]
4
+ whenNotToUse: string[]
5
+ anatomy?: {required: string[]; optional?: string[]}
6
+ relatedTo?: Array<{component: string; relationship: string}>
7
+ example: string
8
+ }
@@ -0,0 +1,16 @@
1
+ import type {ComponentMeta} from '../../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Connector line between two points, with an arrowhead at the end. Supports straight lines and right-angle (L) bends. The arrowhead inherits the line colour via stroke="context-stroke".',
6
+ whenToUse: [
7
+ 'Linking nodes in a flowchart or architecture diagram',
8
+ 'Indicating directionality (the arrowhead points to `to`)',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Dashed callouts to labels — use Leader',
12
+ 'Two-headed/undirected edges — out of scope in v1; render two arrows or a line + custom marker',
13
+ ],
14
+ example: `<Arrow from={[340, 76]} to={[340, 120]} />
15
+ <Arrow from={[280, 246]} to={[170, 300]} bend="L" />`,
16
+ }
@@ -0,0 +1,48 @@
1
+ import type {Point} from '../types'
2
+
3
+ export interface ArrowProps {
4
+ from: Point
5
+ to: Point
6
+ /**
7
+ * `none` (default) draws a straight line.
8
+ * `L` draws a 2-segment bend: horizontal-then-vertical or vertical-then-horizontal.
9
+ * `Z` draws a 3-segment dogleg so the middle leg is detached from both endpoints —
10
+ * useful for branching/converging arrows that should not run along the source or
11
+ * target box's edge.
12
+ * `axis` controls the orientation of the middle leg.
13
+ * - axis='v' (default): leg order is V-H or V-H-V (middle leg horizontal).
14
+ * - axis='h': leg order is H-V or H-V-H (middle leg vertical).
15
+ */
16
+ bend?: 'none' | 'L' | 'Z'
17
+ axis?: 'v' | 'h'
18
+ /**
19
+ * For `Z` bends, the position of the middle leg as a fraction (0..1) between
20
+ * `from` and `to` along the primary axis. Defaults to 0.5 (centered).
21
+ */
22
+ mid?: number
23
+ }
24
+
25
+ export function Arrow({from, to, bend = 'none', axis = 'v', mid = 0.5}: ArrowProps) {
26
+ const [x1, y1] = from
27
+ const [x2, y2] = to
28
+
29
+ if (bend === 'none') {
30
+ return <line className="arr" x1={x1} y1={y1} x2={x2} y2={y2} markerEnd="url(#duro-arrow)" />
31
+ }
32
+
33
+ let d: string
34
+ if (bend === 'L') {
35
+ d = axis === 'v' ? `M ${x1} ${y1} V ${y2} H ${x2}` : `M ${x1} ${y1} H ${x2} V ${y2}`
36
+ } else {
37
+ // Z-bend: 3 segments with the middle leg detached from both endpoints.
38
+ if (axis === 'v') {
39
+ const midY = y1 + (y2 - y1) * mid
40
+ d = `M ${x1} ${y1} V ${midY} H ${x2} V ${y2}`
41
+ } else {
42
+ const midX = x1 + (x2 - x1) * mid
43
+ d = `M ${x1} ${y1} H ${midX} V ${y2} H ${x2}`
44
+ }
45
+ }
46
+
47
+ return <path className="arr" d={d} fill="none" markerEnd="url(#duro-arrow)" />
48
+ }
@@ -0,0 +1,21 @@
1
+ import type {ComponentMeta} from '../../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Root SVG canvas for a static diagram. Provides accessibility (role=img, title, desc), the shared arrow marker definition, and the .duro-diagram scope class that all child node/connector classes resolve under. Authoring is coordinate-explicit; pair with Node, Arrow, Leader, Text.',
6
+ whenToUse: [
7
+ 'Hand-authored flowcharts and architecture diagrams rendered inline',
8
+ 'Static, non-interactive visualisations with controlled coordinates',
9
+ 'Diagrams that must theme alongside the rest of the UI (light/dark/high-contrast via ThemeProvider)',
10
+ ],
11
+ whenNotToUse: [
12
+ 'Auto-laid-out graphs — pick a layout engine (dagre, elkjs) and adapt downstream',
13
+ 'Interactive editors with drag/drop or live data — out of scope',
14
+ 'Charts (bar, line, pie) — use a charting library',
15
+ ],
16
+ example: `<Diagram width={680} height={320} title="Vault PKI flow">
17
+ <Node x={240} y={20} w={200} h={56} ramp="gray" title="Start" />
18
+ <Node x={240} y={120} w={200} h={72} ramp="blue" title="Lookup metadata" />
19
+ <Arrow from={[340, 76]} to={[340, 120]} />
20
+ </Diagram>`,
21
+ }
@@ -0,0 +1,137 @@
1
+ import type {Meta, StoryObj} from '@storybook/react'
2
+ import {expect} from 'storybook/test'
3
+ import {Diagram} from './Diagram'
4
+ import {Node} from '../Node/Node'
5
+ import {Arrow} from '../Arrow/Arrow'
6
+ import {Leader} from '../Leader/Leader'
7
+ import {Text} from '../Text/Text'
8
+ import {RAMPS} from '../types'
9
+
10
+ const meta: Meta<typeof Diagram> = {
11
+ title: 'Diagrams/Diagram',
12
+ component: Diagram,
13
+ }
14
+ export default meta
15
+ type Story = StoryObj<typeof Diagram>
16
+
17
+ /**
18
+ * One swatch row per ramp. Each row shows a Node with both title and subtitle
19
+ * so all four colour stops (fill, stroke, title, subtitle) are exercised. Wrap
20
+ * the story in a different ThemeProvider via the toolbar background switcher
21
+ * to verify dark / light / high-contrast all flip cleanly.
22
+ */
23
+ export const RampSwatches: Story = {
24
+ args: {
25
+ width: 680,
26
+ height: RAMPS.length * 70 + 40,
27
+ title: 'All ramps',
28
+ desc: 'Every ramp rendered as a Node with title + subtitle to exercise all four colour stops.',
29
+ children: (
30
+ <>
31
+ {RAMPS.map((ramp, i) => (
32
+ <Node
33
+ key={ramp}
34
+ x={40}
35
+ y={20 + i * 70}
36
+ w={600}
37
+ h={56}
38
+ ramp={ramp}
39
+ title={`Ramp: ${ramp}`}
40
+ subtitle="Title and subtitle, fill and stroke"
41
+ />
42
+ ))}
43
+ </>
44
+ ),
45
+ },
46
+ play: async ({canvas}) => {
47
+ await expect(canvas.getByText('Ramp: blue')).toBeInTheDocument()
48
+ await expect(canvas.getByText('Ramp: red')).toBeInTheDocument()
49
+ },
50
+ }
51
+
52
+ /**
53
+ * The chat's worked example: an idempotent Vault PKI certificate-issuance flow.
54
+ * Exercises Diagram + Node + Arrow (straight + L-bend) + Leader + Text in one piece.
55
+ *
56
+ * Layout, with the box centres marked:
57
+ *
58
+ * Start (240,20)–(440,76)
59
+ * |
60
+ * Lookup metadata (240,120)–(440,192)
61
+ * |
62
+ * decision split (no diamond — colour split + L-bends carry meaning)
63
+ * / \
64
+ * Cached? Y N Cached?
65
+ * | |
66
+ * Return cached cert Issue new certificate (then back to bottom row)
67
+ * (40,240)–(280,312) (400,240)–(640,312)
68
+ * \ /
69
+ * End (240,360)–(440,416)
70
+ */
71
+ export const VaultPkiFlow: Story = {
72
+ args: {
73
+ width: 680,
74
+ height: 440,
75
+ title: 'Vault PKI idempotent issuance flow',
76
+ desc:
77
+ 'Start → look up KV metadata → branch: cached cert is returned, otherwise a new ' +
78
+ 'cert is issued and metadata is updated. Both branches converge on End.',
79
+ children: (
80
+ <>
81
+ <Node x={240} y={20} w={200} h={56} ramp="gray" title="Start" />
82
+ <Arrow from={[340, 76]} to={[340, 120]} />
83
+
84
+ <Node
85
+ x={240}
86
+ y={120}
87
+ w={200}
88
+ h={72}
89
+ ramp="blue"
90
+ title="Lookup metadata"
91
+ subtitle="Read KV at secret/pki/<cn>"
92
+ />
93
+
94
+ <Arrow from={[340, 192]} to={[140, 240]} bend="Z" />
95
+ <Arrow from={[340, 192]} to={[520, 240]} bend="Z" />
96
+
97
+ <Node
98
+ x={40}
99
+ y={240}
100
+ w={200}
101
+ h={72}
102
+ ramp="teal"
103
+ title="Return cached cert"
104
+ subtitle="Skip issuance"
105
+ />
106
+
107
+ <Node
108
+ x={420}
109
+ y={240}
110
+ w={220}
111
+ h={72}
112
+ ramp="coral"
113
+ title="Issue new certificate"
114
+ subtitle="POST pki/issue/<role>"
115
+ />
116
+
117
+ <Arrow from={[140, 312]} to={[340, 360]} bend="Z" />
118
+ <Arrow from={[530, 312]} to={[340, 360]} bend="Z" />
119
+
120
+ <Node x={240} y={360} w={200} h={56} ramp="gray" title="End" />
121
+
122
+ {/* Edge label on the right branch arrow's horizontal leg (y=216, x=340..520).
123
+ Centered on the midpoint, offset 8px above so it doesn't sit on the line. */}
124
+ <Text x={430} y={208} variant="ts" anchor="middle">
125
+ Coral encodes a write
126
+ </Text>
127
+ </>
128
+ ),
129
+ },
130
+ play: async ({canvas}) => {
131
+ await expect(canvas.getByText('Start')).toBeInTheDocument()
132
+ await expect(canvas.getByText('Lookup metadata')).toBeInTheDocument()
133
+ await expect(canvas.getByText('Return cached cert')).toBeInTheDocument()
134
+ await expect(canvas.getByText('Issue new certificate')).toBeInTheDocument()
135
+ await expect(canvas.getByText('End')).toBeInTheDocument()
136
+ },
137
+ }
@@ -0,0 +1,47 @@
1
+ import type {ReactNode} from 'react'
2
+
3
+ export interface DiagramProps {
4
+ width: number
5
+ height: number
6
+ title: string
7
+ desc?: string
8
+ viewBox?: string
9
+ children: ReactNode
10
+ }
11
+
12
+ export function Diagram({width, height, title, desc, viewBox, children}: DiagramProps) {
13
+ return (
14
+ <svg
15
+ role="img"
16
+ className="duro-diagram"
17
+ viewBox={viewBox ?? `0 0 ${width} ${height}`}
18
+ width="100%"
19
+ style={{maxWidth: width, height: 'auto', display: 'block'}}
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ >
22
+ <title>{title}</title>
23
+ {desc ? <desc>{desc}</desc> : null}
24
+ <defs>
25
+ <marker
26
+ id="duro-arrow"
27
+ viewBox="0 0 10 10"
28
+ refX={8}
29
+ refY={5}
30
+ markerWidth={6}
31
+ markerHeight={6}
32
+ orient="auto-start-reverse"
33
+ >
34
+ <path
35
+ d="M2 1L8 5L2 9"
36
+ fill="none"
37
+ stroke="context-stroke"
38
+ strokeWidth={1.5}
39
+ strokeLinecap="round"
40
+ strokeLinejoin="round"
41
+ />
42
+ </marker>
43
+ </defs>
44
+ {children}
45
+ </svg>
46
+ )
47
+ }
@@ -0,0 +1,13 @@
1
+ import type {ComponentMeta} from '../../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'A dashed, thin line for callout/annotation leaders. No arrowhead. Use to point a label at a node or region without implying flow.',
6
+ whenToUse: [
7
+ 'Connecting an annotation Text to its referenced shape',
8
+ 'Visually grouping a label with a region of the diagram',
9
+ ],
10
+ whenNotToUse: ['Indicating directional flow — use Arrow'],
11
+ example: `<Leader from={[300, 100]} to={[420, 80]} />
12
+ <Text x={425} y={80} variant="ts" anchor="start">Annotation</Text>`,
13
+ }
@@ -0,0 +1,12 @@
1
+ import type {Point} from '../types'
2
+
3
+ export interface LeaderProps {
4
+ from: Point
5
+ to: Point
6
+ }
7
+
8
+ export function Leader({from, to}: LeaderProps) {
9
+ const [x1, y1] = from
10
+ const [x2, y2] = to
11
+ return <line className="leader" x1={x1} y1={y1} x2={x2} y2={y2} />
12
+ }
@@ -0,0 +1,21 @@
1
+ import type {ComponentMeta} from '../../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'A rounded rectangle node with a title and optional subtitle. Coloured via a ramp prop; fill, stroke, and text colours flip automatically with the active theme.',
6
+ whenToUse: [
7
+ 'Steps in a flowchart',
8
+ 'Components in an architecture diagram',
9
+ 'Boxes representing services, modules, or resources',
10
+ ],
11
+ whenNotToUse: [
12
+ 'Free-floating text — use Text',
13
+ 'Container shapes without semantic content — use a plain <rect className="box">',
14
+ ],
15
+ example: `<Node
16
+ x={100} y={20} w={200} h={56}
17
+ ramp="blue"
18
+ title="Lookup metadata"
19
+ subtitle="O(1) cache check"
20
+ />`,
21
+ }
@@ -0,0 +1,32 @@
1
+ import type {Ramp} from '../types'
2
+
3
+ export interface NodeProps {
4
+ x: number
5
+ y: number
6
+ w: number
7
+ h: number
8
+ ramp: Ramp
9
+ title: string
10
+ subtitle?: string
11
+ rx?: number
12
+ }
13
+
14
+ export function Node({x, y, w, h, ramp, title, subtitle, rx = 8}: NodeProps) {
15
+ const cx = x + w / 2
16
+ const titleY = subtitle ? y + h / 2 - 9 : y + h / 2
17
+ const subtitleY = y + h / 2 + 9
18
+
19
+ return (
20
+ <g className={`node c-${ramp}`}>
21
+ <rect x={x} y={y} width={w} height={h} rx={rx} strokeWidth={0.5} />
22
+ <text className="th" x={cx} y={titleY} textAnchor="middle" dominantBaseline="central">
23
+ {title}
24
+ </text>
25
+ {subtitle ? (
26
+ <text className="ts" x={cx} y={subtitleY} textAnchor="middle" dominantBaseline="central">
27
+ {subtitle}
28
+ </text>
29
+ ) : null}
30
+ </g>
31
+ )
32
+ }
@@ -0,0 +1,14 @@
1
+ import type {ComponentMeta} from '../../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Free-floating text inside a Diagram. Three variants — `t` (body 14px), `ts` (subtitle 12px muted), `th` (title 14px medium). Baseline is centred; descenders sit on the visual line.',
6
+ whenToUse: [
7
+ 'Annotations and callouts pointing to nodes',
8
+ 'Section headers within a diagram',
9
+ 'Standalone labels not attached to a Node',
10
+ ],
11
+ whenNotToUse: ['Titles inside a Node — Node already renders its own title/subtitle'],
12
+ example: `<Text x={20} y={20} variant="th">System overview</Text>
13
+ <Text x={20} y={40} variant="ts">As of 2026-05</Text>`,
14
+ }
@@ -0,0 +1,20 @@
1
+ import type {ReactNode} from 'react'
2
+
3
+ export type TextVariant = 't' | 'ts' | 'th'
4
+ export type TextAnchor = 'start' | 'middle' | 'end'
5
+
6
+ export interface TextProps {
7
+ x: number
8
+ y: number
9
+ variant?: TextVariant
10
+ anchor?: TextAnchor
11
+ children: ReactNode
12
+ }
13
+
14
+ export function Text({x, y, variant = 't', anchor = 'start', children}: TextProps) {
15
+ return (
16
+ <text className={variant} x={x} y={y} textAnchor={anchor} dominantBaseline="central">
17
+ {children}
18
+ </text>
19
+ )
20
+ }
@@ -0,0 +1,24 @@
1
+ export type Ramp =
2
+ | 'purple'
3
+ | 'teal'
4
+ | 'coral'
5
+ | 'pink'
6
+ | 'gray'
7
+ | 'blue'
8
+ | 'green'
9
+ | 'amber'
10
+ | 'red'
11
+
12
+ export const RAMPS: readonly Ramp[] = [
13
+ 'purple',
14
+ 'teal',
15
+ 'coral',
16
+ 'pink',
17
+ 'gray',
18
+ 'blue',
19
+ 'green',
20
+ 'amber',
21
+ 'red',
22
+ ] as const
23
+
24
+ export type Point = readonly [number, number]
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import './styles.css'
2
+
3
+ export {Diagram, type DiagramProps} from './components/Diagram/Diagram'
4
+ export {Node, type NodeProps} from './components/Node/Node'
5
+ export {Arrow, type ArrowProps} from './components/Arrow/Arrow'
6
+ export {Leader, type LeaderProps} from './components/Leader/Leader'
7
+ export {Text, type TextProps, type TextVariant, type TextAnchor} from './components/Text/Text'
8
+ export {RAMPS, type Ramp, type Point} from './components/types'
package/src/styles.css ADDED
@@ -0,0 +1,359 @@
1
+ /*
2
+ * @duro-app/diagrams — design system for static SVG diagrams.
3
+ *
4
+ * Single source of truth for the 9 colour ramps × 7 stops. Light/dark/high-contrast
5
+ * themes pick stops from this table. All selectors are scoped under `.duro-diagram`
6
+ * so the package never bleeds class names like `.t`/`.box`/`.arr`/`.c-blue` into a
7
+ * host page. Themes are driven by the `data-theme` attribute set by ThemeProvider.
8
+ *
9
+ * Ramp reference (the 7 stops per ramp):
10
+ *
11
+ * 50 100 200 600 700 800 900
12
+ * purple #f5f3ff #ede9fe #ddd6fe #7c3aed #6d28d9 #5b21b6 #4c1d95
13
+ * teal #f0fdfa #ccfbf1 #99f6e4 #0d9488 #0f766e #115e59 #134e4a
14
+ * coral #fff1f2 #ffe4e6 #fecdd3 #e11d48 #be123c #9f1239 #881337
15
+ * pink #fdf2f8 #fce7f3 #fbcfe8 #db2777 #be185d #9d174d #831843
16
+ * gray #f5f5f5 #e5e5e5 #d4d4d4 #525252 #404040 #262626 #171717
17
+ * blue #eff6ff #dbeafe #bfdbfe #2563eb #1d4ed8 #1e3a8a #1e1b4b
18
+ * green #f0fdf4 #dcfce7 #bbf7d0 #16a34a #15803d #166534 #14532d
19
+ * amber #fffbeb #fef3c7 #fde68a #d97706 #b45309 #92400e #78350f
20
+ * red #fef2f2 #fee2e2 #fecaca #dc2626 #b91c1c #991b1b #7f1d1d
21
+ *
22
+ * Per-theme stop selection:
23
+ * light: Fill = 50, Stroke = 600, Title = 800, Subtitle = 600
24
+ * dark: Fill = 800, Stroke = 200, Title = 100, Subtitle = 200
25
+ * high-contrast: Fill = 900, Stroke = 100, Title = 50, Subtitle = 100
26
+ */
27
+
28
+ /* ============================================================
29
+ * Theme variables
30
+ * ============================================================ */
31
+
32
+ /* Default = dark. Applies when there is no [data-theme] ancestor or it is 'dark'. */
33
+ :where(:root, [data-theme='dark']) {
34
+ --duro-diagram-purple-fill: #5b21b6;
35
+ --duro-diagram-purple-stroke: #ddd6fe;
36
+ --duro-diagram-purple-title: #ede9fe;
37
+ --duro-diagram-purple-subtitle: #ddd6fe;
38
+
39
+ --duro-diagram-teal-fill: #115e59;
40
+ --duro-diagram-teal-stroke: #99f6e4;
41
+ --duro-diagram-teal-title: #ccfbf1;
42
+ --duro-diagram-teal-subtitle: #99f6e4;
43
+
44
+ --duro-diagram-coral-fill: #9f1239;
45
+ --duro-diagram-coral-stroke: #fecdd3;
46
+ --duro-diagram-coral-title: #ffe4e6;
47
+ --duro-diagram-coral-subtitle: #fecdd3;
48
+
49
+ --duro-diagram-pink-fill: #9d174d;
50
+ --duro-diagram-pink-stroke: #fbcfe8;
51
+ --duro-diagram-pink-title: #fce7f3;
52
+ --duro-diagram-pink-subtitle: #fbcfe8;
53
+
54
+ --duro-diagram-gray-fill: #262626;
55
+ --duro-diagram-gray-stroke: #d4d4d4;
56
+ --duro-diagram-gray-title: #e5e5e5;
57
+ --duro-diagram-gray-subtitle: #d4d4d4;
58
+
59
+ --duro-diagram-blue-fill: #1e3a8a;
60
+ --duro-diagram-blue-stroke: #bfdbfe;
61
+ --duro-diagram-blue-title: #dbeafe;
62
+ --duro-diagram-blue-subtitle: #bfdbfe;
63
+
64
+ --duro-diagram-green-fill: #166534;
65
+ --duro-diagram-green-stroke: #bbf7d0;
66
+ --duro-diagram-green-title: #dcfce7;
67
+ --duro-diagram-green-subtitle: #bbf7d0;
68
+
69
+ --duro-diagram-amber-fill: #92400e;
70
+ --duro-diagram-amber-stroke: #fde68a;
71
+ --duro-diagram-amber-title: #fef3c7;
72
+ --duro-diagram-amber-subtitle: #fde68a;
73
+
74
+ --duro-diagram-red-fill: #991b1b;
75
+ --duro-diagram-red-stroke: #fecaca;
76
+ --duro-diagram-red-title: #fee2e2;
77
+ --duro-diagram-red-subtitle: #fecaca;
78
+
79
+ --duro-diagram-arrow-stroke: #6b6b6b;
80
+ --duro-diagram-leader-stroke: #4a4a4a;
81
+ --duro-diagram-box-fill: transparent;
82
+ --duro-diagram-box-stroke: #333333;
83
+ --duro-diagram-text: #e5e5e5;
84
+ --duro-diagram-text-muted: #b0b0b0;
85
+ }
86
+
87
+ [data-theme='light'] {
88
+ --duro-diagram-purple-fill: #f5f3ff;
89
+ --duro-diagram-purple-stroke: #7c3aed;
90
+ --duro-diagram-purple-title: #5b21b6;
91
+ --duro-diagram-purple-subtitle: #7c3aed;
92
+
93
+ --duro-diagram-teal-fill: #f0fdfa;
94
+ --duro-diagram-teal-stroke: #0d9488;
95
+ --duro-diagram-teal-title: #115e59;
96
+ --duro-diagram-teal-subtitle: #0d9488;
97
+
98
+ --duro-diagram-coral-fill: #fff1f2;
99
+ --duro-diagram-coral-stroke: #e11d48;
100
+ --duro-diagram-coral-title: #9f1239;
101
+ --duro-diagram-coral-subtitle: #e11d48;
102
+
103
+ --duro-diagram-pink-fill: #fdf2f8;
104
+ --duro-diagram-pink-stroke: #db2777;
105
+ --duro-diagram-pink-title: #9d174d;
106
+ --duro-diagram-pink-subtitle: #db2777;
107
+
108
+ --duro-diagram-gray-fill: #f5f5f5;
109
+ --duro-diagram-gray-stroke: #525252;
110
+ --duro-diagram-gray-title: #262626;
111
+ --duro-diagram-gray-subtitle: #525252;
112
+
113
+ --duro-diagram-blue-fill: #eff6ff;
114
+ --duro-diagram-blue-stroke: #2563eb;
115
+ --duro-diagram-blue-title: #1e3a8a;
116
+ --duro-diagram-blue-subtitle: #2563eb;
117
+
118
+ --duro-diagram-green-fill: #f0fdf4;
119
+ --duro-diagram-green-stroke: #16a34a;
120
+ --duro-diagram-green-title: #166534;
121
+ --duro-diagram-green-subtitle: #16a34a;
122
+
123
+ --duro-diagram-amber-fill: #fffbeb;
124
+ --duro-diagram-amber-stroke: #d97706;
125
+ --duro-diagram-amber-title: #92400e;
126
+ --duro-diagram-amber-subtitle: #d97706;
127
+
128
+ --duro-diagram-red-fill: #fef2f2;
129
+ --duro-diagram-red-stroke: #dc2626;
130
+ --duro-diagram-red-title: #991b1b;
131
+ --duro-diagram-red-subtitle: #dc2626;
132
+
133
+ --duro-diagram-arrow-stroke: #888888;
134
+ --duro-diagram-leader-stroke: #c0c0c0;
135
+ --duro-diagram-box-fill: transparent;
136
+ --duro-diagram-box-stroke: #d4d4d4;
137
+ --duro-diagram-text: #1a1a1a;
138
+ --duro-diagram-text-muted: #4a4a4a;
139
+ }
140
+
141
+ [data-theme='high-contrast'] {
142
+ --duro-diagram-purple-fill: #4c1d95;
143
+ --duro-diagram-purple-stroke: #ede9fe;
144
+ --duro-diagram-purple-title: #f5f3ff;
145
+ --duro-diagram-purple-subtitle: #ede9fe;
146
+
147
+ --duro-diagram-teal-fill: #134e4a;
148
+ --duro-diagram-teal-stroke: #ccfbf1;
149
+ --duro-diagram-teal-title: #f0fdfa;
150
+ --duro-diagram-teal-subtitle: #ccfbf1;
151
+
152
+ --duro-diagram-coral-fill: #881337;
153
+ --duro-diagram-coral-stroke: #ffe4e6;
154
+ --duro-diagram-coral-title: #fff1f2;
155
+ --duro-diagram-coral-subtitle: #ffe4e6;
156
+
157
+ --duro-diagram-pink-fill: #831843;
158
+ --duro-diagram-pink-stroke: #fce7f3;
159
+ --duro-diagram-pink-title: #fdf2f8;
160
+ --duro-diagram-pink-subtitle: #fce7f3;
161
+
162
+ --duro-diagram-gray-fill: #171717;
163
+ --duro-diagram-gray-stroke: #e5e5e5;
164
+ --duro-diagram-gray-title: #f5f5f5;
165
+ --duro-diagram-gray-subtitle: #e5e5e5;
166
+
167
+ --duro-diagram-blue-fill: #1e1b4b;
168
+ --duro-diagram-blue-stroke: #dbeafe;
169
+ --duro-diagram-blue-title: #eff6ff;
170
+ --duro-diagram-blue-subtitle: #dbeafe;
171
+
172
+ --duro-diagram-green-fill: #14532d;
173
+ --duro-diagram-green-stroke: #dcfce7;
174
+ --duro-diagram-green-title: #f0fdf4;
175
+ --duro-diagram-green-subtitle: #dcfce7;
176
+
177
+ --duro-diagram-amber-fill: #78350f;
178
+ --duro-diagram-amber-stroke: #fef3c7;
179
+ --duro-diagram-amber-title: #fffbeb;
180
+ --duro-diagram-amber-subtitle: #fef3c7;
181
+
182
+ --duro-diagram-red-fill: #7f1d1d;
183
+ --duro-diagram-red-stroke: #fee2e2;
184
+ --duro-diagram-red-title: #fef2f2;
185
+ --duro-diagram-red-subtitle: #fee2e2;
186
+
187
+ --duro-diagram-arrow-stroke: #ffffff;
188
+ --duro-diagram-leader-stroke: #888888;
189
+ --duro-diagram-box-fill: transparent;
190
+ --duro-diagram-box-stroke: #555555;
191
+ --duro-diagram-text: #ffffff;
192
+ --duro-diagram-text-muted: #b0b0b0;
193
+ }
194
+
195
+ /* ============================================================
196
+ * Typography (scoped under .duro-diagram)
197
+ * ============================================================ */
198
+
199
+ .duro-diagram .t {
200
+ font:
201
+ 400 14px/1.4 -apple-system,
202
+ BlinkMacSystemFont,
203
+ 'Segoe UI',
204
+ Roboto,
205
+ sans-serif;
206
+ fill: var(--duro-diagram-text);
207
+ stroke: none;
208
+ }
209
+
210
+ .duro-diagram .ts {
211
+ font:
212
+ 400 12px/1.4 -apple-system,
213
+ BlinkMacSystemFont,
214
+ 'Segoe UI',
215
+ Roboto,
216
+ sans-serif;
217
+ fill: var(--duro-diagram-text-muted);
218
+ stroke: none;
219
+ }
220
+
221
+ .duro-diagram .th {
222
+ font:
223
+ 500 14px/1.4 -apple-system,
224
+ BlinkMacSystemFont,
225
+ 'Segoe UI',
226
+ Roboto,
227
+ sans-serif;
228
+ fill: var(--duro-diagram-text);
229
+ stroke: none;
230
+ }
231
+
232
+ /* ============================================================
233
+ * Structural classes
234
+ * ============================================================ */
235
+
236
+ .duro-diagram .box {
237
+ fill: var(--duro-diagram-box-fill);
238
+ stroke: var(--duro-diagram-box-stroke);
239
+ stroke-width: 0.5;
240
+ }
241
+
242
+ .duro-diagram .arr {
243
+ stroke: var(--duro-diagram-arrow-stroke);
244
+ stroke-width: 1.5;
245
+ fill: none;
246
+ }
247
+
248
+ .duro-diagram .leader {
249
+ stroke: var(--duro-diagram-leader-stroke);
250
+ stroke-width: 0.5;
251
+ stroke-dasharray: 3 3;
252
+ fill: none;
253
+ }
254
+
255
+ /* `.node` is a structural wrapper. Colour is applied via the `.c-{ramp}` sibling
256
+ * class on the same <g> — see ramp rules below. */
257
+
258
+ /* ============================================================
259
+ * Ramps — fill/stroke on the <rect>, text colour on .th/.ts children
260
+ * ============================================================ */
261
+
262
+ .duro-diagram .c-purple > rect {
263
+ fill: var(--duro-diagram-purple-fill);
264
+ stroke: var(--duro-diagram-purple-stroke);
265
+ }
266
+ .duro-diagram .c-purple > .th {
267
+ fill: var(--duro-diagram-purple-title);
268
+ }
269
+ .duro-diagram .c-purple > .ts {
270
+ fill: var(--duro-diagram-purple-subtitle);
271
+ }
272
+
273
+ .duro-diagram .c-teal > rect {
274
+ fill: var(--duro-diagram-teal-fill);
275
+ stroke: var(--duro-diagram-teal-stroke);
276
+ }
277
+ .duro-diagram .c-teal > .th {
278
+ fill: var(--duro-diagram-teal-title);
279
+ }
280
+ .duro-diagram .c-teal > .ts {
281
+ fill: var(--duro-diagram-teal-subtitle);
282
+ }
283
+
284
+ .duro-diagram .c-coral > rect {
285
+ fill: var(--duro-diagram-coral-fill);
286
+ stroke: var(--duro-diagram-coral-stroke);
287
+ }
288
+ .duro-diagram .c-coral > .th {
289
+ fill: var(--duro-diagram-coral-title);
290
+ }
291
+ .duro-diagram .c-coral > .ts {
292
+ fill: var(--duro-diagram-coral-subtitle);
293
+ }
294
+
295
+ .duro-diagram .c-pink > rect {
296
+ fill: var(--duro-diagram-pink-fill);
297
+ stroke: var(--duro-diagram-pink-stroke);
298
+ }
299
+ .duro-diagram .c-pink > .th {
300
+ fill: var(--duro-diagram-pink-title);
301
+ }
302
+ .duro-diagram .c-pink > .ts {
303
+ fill: var(--duro-diagram-pink-subtitle);
304
+ }
305
+
306
+ .duro-diagram .c-gray > rect {
307
+ fill: var(--duro-diagram-gray-fill);
308
+ stroke: var(--duro-diagram-gray-stroke);
309
+ }
310
+ .duro-diagram .c-gray > .th {
311
+ fill: var(--duro-diagram-gray-title);
312
+ }
313
+ .duro-diagram .c-gray > .ts {
314
+ fill: var(--duro-diagram-gray-subtitle);
315
+ }
316
+
317
+ .duro-diagram .c-blue > rect {
318
+ fill: var(--duro-diagram-blue-fill);
319
+ stroke: var(--duro-diagram-blue-stroke);
320
+ }
321
+ .duro-diagram .c-blue > .th {
322
+ fill: var(--duro-diagram-blue-title);
323
+ }
324
+ .duro-diagram .c-blue > .ts {
325
+ fill: var(--duro-diagram-blue-subtitle);
326
+ }
327
+
328
+ .duro-diagram .c-green > rect {
329
+ fill: var(--duro-diagram-green-fill);
330
+ stroke: var(--duro-diagram-green-stroke);
331
+ }
332
+ .duro-diagram .c-green > .th {
333
+ fill: var(--duro-diagram-green-title);
334
+ }
335
+ .duro-diagram .c-green > .ts {
336
+ fill: var(--duro-diagram-green-subtitle);
337
+ }
338
+
339
+ .duro-diagram .c-amber > rect {
340
+ fill: var(--duro-diagram-amber-fill);
341
+ stroke: var(--duro-diagram-amber-stroke);
342
+ }
343
+ .duro-diagram .c-amber > .th {
344
+ fill: var(--duro-diagram-amber-title);
345
+ }
346
+ .duro-diagram .c-amber > .ts {
347
+ fill: var(--duro-diagram-amber-subtitle);
348
+ }
349
+
350
+ .duro-diagram .c-red > rect {
351
+ fill: var(--duro-diagram-red-fill);
352
+ stroke: var(--duro-diagram-red-stroke);
353
+ }
354
+ .duro-diagram .c-red > .th {
355
+ fill: var(--duro-diagram-red-title);
356
+ }
357
+ .duro-diagram .c-red > .ts {
358
+ fill: var(--duro-diagram-red-subtitle);
359
+ }