@tekkare/romulus 0.1.0 → 0.1.7

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.0",
7
+ "version": "0.1.6",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -10,18 +10,30 @@ const module = defineNuxtModule({
10
10
  }
11
11
  },
12
12
  defaults: {
13
- prefix: "R"
13
+ prefix: "Rm"
14
14
  },
15
15
  async setup(options, nuxt) {
16
16
  const { resolve } = createResolver(import.meta.url);
17
17
  const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
18
18
  nuxt.options.css.push(resolve(runtimeDir, "assets/styles/tokens.css"));
19
19
  nuxt.options.css.push(resolve(runtimeDir, "assets/styles/base.css"));
20
+ nuxt.options.css.push(resolve(runtimeDir, "assets/styles/components.css"));
21
+ nuxt.options.css.push(resolve(runtimeDir, "assets/styles/utilities.css"));
20
22
  await addComponentsDir({
21
23
  path: resolve(runtimeDir, "components"),
22
24
  prefix: options.prefix
23
25
  });
24
26
  addImportsDir(resolve(runtimeDir, "composables"));
27
+ addImportsDir(resolve(runtimeDir, "utils"));
28
+ nuxt.options.app.head.link = nuxt.options.app.head.link || [];
29
+ const fontsHref = "https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&family=Caudex:wght@400;700&display=swap";
30
+ if (!nuxt.options.app.head.link.some((l) => l.href === fontsHref)) {
31
+ nuxt.options.app.head.link.push(
32
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
33
+ { rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: "" },
34
+ { rel: "stylesheet", href: fontsHref }
35
+ );
36
+ }
25
37
  }
26
38
  });
27
39
 
@@ -0,0 +1 @@
1
+ .grid-2{grid-template-columns:repeat(2,1fr)}.grid-2,.grid-3{display:grid;gap:20px}.grid-3{grid-template-columns:repeat(3,1fr)}.grid-4{display:grid;gap:16px;grid-template-columns:repeat(4,1fr)}.grid-2-1{display:grid;gap:20px;grid-template-columns:2fr 1fr}.chart-card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:10px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;transition:box-shadow .18s,border-color .18s}.chart-card:hover{border-color:var(--color-violet-pale);box-shadow:0 6px 16px rgba(107,63,255,.1),0 1px 3px rgba(30,41,59,.04)}.chart-card-head{padding:22px 64px 14px 24px;position:relative}.chart-card-title{color:var(--text-primary);font-size:15px;font-weight:700;letter-spacing:-.01em;line-height:1.3;margin:0 0 5px}.chart-card-context{color:var(--text-muted);font-size:12px;line-height:1.55;margin:0}.chart-card-actions{position:absolute;right:12px;top:12px}.cc-export{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;color:var(--text-secondary);cursor:pointer;display:flex;font-size:14px;height:28px;justify-content:center;transition:background .15s,color .15s,border-color .15s;width:28px}.cc-export:hover{color:var(--color-secondary)}.cc-export:hover,.chart-card-actions.open .cc-export{background:var(--bg-hover);border-color:var(--color-violet-pale)}.chart-card-actions.open .cc-export{color:var(--color-primary)}.cc-menu{animation:cc-menu-in .12s cubic-bezier(.2,0,0,1);background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:var(--shadow-lg);display:none;min-width:200px;padding:6px 0;position:absolute;right:0;top:34px;z-index:var(--z-modal)}.chart-card-actions.open .cc-menu{display:block}.cc-menu-section{color:var(--text-muted);font-size:10px;font-weight:700;letter-spacing:.08em;padding:8px 14px 4px;text-transform:uppercase}.cc-menu-item{align-items:center;background:none;border:0;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:13px;gap:10px;padding:7px 14px;text-align:left;width:100%}.cc-menu-item i{color:var(--text-muted);font-size:14px}.cc-menu-item:hover{background:var(--bg-surface-3);color:var(--text-primary)}.cc-menu-item:hover i{color:var(--text-secondary)}.cc-menu-divider{background:var(--bg-surface-3);height:1px;margin:4px 0}@keyframes cc-menu-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.chart-card-body{flex:1;padding:4px 12px 8px}.chart-card-foot{align-items:center;background:var(--bg-surface-2);border-radius:0 0 10px 10px;border-top:1px solid var(--border-soft);color:var(--text-muted);display:flex;flex-wrap:wrap;font-size:11px;gap:10px;padding:12px 24px}.chart-card-foot .src{align-items:center;display:flex;gap:4px}.chart-card-foot .src b{color:var(--text-secondary);font-weight:600}.chart-card-foot .dot{background:var(--border-default);border-radius:50%;height:3px;width:3px}.chart-card-foot .updated{color:var(--text-secondary);font-weight:500}.kpi-card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:0 2px 8px rgba(30,41,59,.06);padding:16px 18px}.kpi-label{color:var(--text-muted);font-size:11px;font-weight:600;letter-spacing:.1em;margin:0 0 6px;text-transform:uppercase}.kpi-value{color:var(--color-primary);font-family:var(--font-serif);font-size:28px;font-weight:700;line-height:1;margin:0}.kpi-value.healthcare{color:var(--color-healthcare)}.kpi-value.discovery{color:var(--color-discovery)}.kpi-value.pharma{color:var(--color-pharma)}.kpi-value.databank{color:var(--color-databank)}.kpi-trend{align-items:center;display:inline-flex;font-size:12px;font-weight:600;gap:4px;margin-top:6px}.kpi-trend i{font-size:11px}.kpi-trend--up{color:var(--color-success)}.kpi-trend--down{color:var(--color-danger)}.kpi-foot,.kpi-trend--flat{color:var(--text-muted)}.kpi-foot{font-size:11px;margin-top:4px}.badge{border-radius:4px;display:inline-block;font-size:10px;font-weight:600;padding:2px 8px}.badge-active{background:var(--color-success-bg);color:var(--color-success)}.badge-pending{background:var(--color-warning-bg);color:var(--color-warning)}.badge-error{background:var(--color-danger-bg);color:var(--color-danger)}.badge-info{background:var(--bg-hover);color:var(--color-primary)}.badge-grey{background:var(--bg-surface-3);color:var(--text-secondary)}table.tk{border-collapse:collapse;font-size:13px;width:100%}.sr-only,table.tk>caption.sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;white-space:nowrap}table.tk caption{caption-side:top;color:var(--text-primary);font-size:var(--font-13);font-weight:var(--weight-bold);padding:0 0 var(--space-8);text-align:left}table.tk th{background:var(--bg-surface-2);border-bottom:1px solid var(--border-default);color:var(--text-secondary);font-size:11px;font-weight:700;letter-spacing:.08em;padding:12px 14px;text-align:left;text-transform:uppercase}table.tk tbody th[scope=row]{background:transparent;font-size:var(--font-13);font-weight:var(--weight-bold);letter-spacing:0;text-transform:none}table.tk tbody th[scope=row],table.tk td{border-bottom:1px solid var(--border-soft);color:var(--text-primary)}table.tk td{padding:12px 14px}table.tk tbody tr:nth-child(2n) td,table.tk tbody tr:nth-child(2n) th[scope=row]{background:var(--bg-surface-2)}table.tk tbody tr:hover td,table.tk tbody tr:hover th[scope=row]{background:var(--bg-hover)}table.tk th[aria-sort]{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}table.tk th[aria-sort]:hover{color:var(--text-primary)}.tk-wrap{overflow-x:auto}.tk-num{font-variant-numeric:tabular-nums;text-align:right}.nav-group{display:flex;flex-direction:column}.nav-group-header{align-items:center;background:none;border:0;color:var(--text-muted);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:11px;font-weight:700;gap:8px;letter-spacing:.08em;padding:8px 12px;text-align:left;text-transform:uppercase;transition:color var(--duration-fast) var(--ease-out);width:100%}.nav-group-header:hover{color:var(--text-secondary)}.nav-group-header .caret{display:inline-flex;margin-left:auto;transition:transform var(--duration-fast) var(--ease-out)}.nav-group[data-open=true]>.nav-group-header{color:var(--color-primary)}.nav-group[data-open=true]>.nav-group-header .caret{transform:rotate(180deg)}.nav-group-items{display:flex;flex-direction:column;gap:2px;padding:2px 0 8px}.cc-export:focus-visible,.cc-menu-item:focus-visible,.chart-card :focus-visible,.nav-group-header:focus-visible,table.tk th[aria-sort]:focus-visible{box-shadow:var(--shadow-focus);outline:2px solid var(--color-secondary);outline-offset:2px}.cc-menu-item,.nav-group-header{min-height:40px}body.text-large .kpi-label{font-size:13px}body.text-large .kpi-value{font-size:34px}body.text-large .kpi-trend{font-size:14px}body.text-large .kpi-foot{font-size:13px}body.text-large .chart-card-title{font-size:17px}body.text-large .chart-card-context{font-size:14px}body.text-large .chart-card-foot{font-size:12px}body.text-large .badge{font-size:12px;padding:3px 9px}body.text-large table.tk{font-size:15px}body.text-large table.tk th{font-size:13px}body.text-xl .kpi-label{font-size:14px}body.text-xl .kpi-value{font-size:40px}body.text-xl .kpi-trend{font-size:16px}body.text-xl .kpi-foot{font-size:14px}body.text-xl .chart-card-title{font-size:20px}body.text-xl .chart-card-context{font-size:16px}body.text-xl .chart-card-foot{font-size:14px}body.text-xl .badge{font-size:14px;padding:3px 10px}body.text-xl table.tk{font-size:16px}body.text-xl table.tk th{font-size:14px}body.high-contrast .cc-export,body.high-contrast .cc-menu,body.high-contrast .chart-card,body.high-contrast .kpi-card{border-width:2px}body.high-contrast .chart-card:hover{border-color:var(--border-default)}
@@ -1 +1 @@
1
- :root{--r-color-primary-50:#eef2ff;--r-color-primary-100:#e0e7ff;--r-color-primary-200:#c7d2fe;--r-color-primary-300:#a5b4fc;--r-color-primary-400:#818cf8;--r-color-primary-500:#6366f1;--r-color-primary-600:#4f46e5;--r-color-primary-700:#4338ca;--r-color-primary-800:#3730a3;--r-color-primary-900:#312e81;--r-color-gray-50:#f9fafb;--r-color-gray-100:#f3f4f6;--r-color-gray-200:#e5e7eb;--r-color-gray-300:#d1d5db;--r-color-gray-400:#9ca3af;--r-color-gray-500:#6b7280;--r-color-gray-600:#4b5563;--r-color-gray-700:#374151;--r-color-gray-800:#1f2937;--r-color-gray-900:#111827;--r-color-gray-950:#030712;--r-color-success-500:#22c55e;--r-color-success-600:#16a34a;--r-color-warning-500:#f59e0b;--r-color-warning-600:#d97706;--r-color-danger-500:#ef4444;--r-color-danger-600:#dc2626;--r-color-white:#fff;--r-color-black:#000;--r-font-family:"Inter",ui-sans-serif,system-ui,-apple-system,sans-serif;--r-font-mono:"JetBrains Mono",ui-monospace,"Cascadia Code",monospace;--r-text-xs:0.75rem;--r-text-sm:0.875rem;--r-text-base:1rem;--r-text-lg:1.125rem;--r-text-xl:1.25rem;--r-text-2xl:1.5rem;--r-text-3xl:1.875rem;--r-text-4xl:2.25rem;--r-font-weight-normal:400;--r-font-weight-medium:500;--r-font-weight-semibold:600;--r-font-weight-bold:700;--r-leading-tight:1.25;--r-leading-normal:1.5;--r-leading-relaxed:1.75;--r-space-0:0;--r-space-1:0.25rem;--r-space-2:0.5rem;--r-space-3:0.75rem;--r-space-4:1rem;--r-space-5:1.25rem;--r-space-6:1.5rem;--r-space-8:2rem;--r-space-10:2.5rem;--r-space-12:3rem;--r-space-16:4rem;--r-space-20:5rem;--r-radius-none:0;--r-radius-sm:0.25rem;--r-radius-md:0.375rem;--r-radius-lg:0.5rem;--r-radius-xl:0.75rem;--r-radius-2xl:1rem;--r-radius-full:9999px;--r-shadow-xs:0 1px 2px 0 rgba(0,0,0,.05);--r-shadow-sm:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--r-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--r-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--r-shadow-xl:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--r-transition-fast:150ms cubic-bezier(0.4,0,0.2,1);--r-transition-base:200ms cubic-bezier(0.4,0,0.2,1);--r-transition-slow:300ms cubic-bezier(0.4,0,0.2,1)}
1
+ :root{--sidebar-w:220px;--sidebar-collapsed-w:56px;--rsidebar-w:280px;--rsidebar-collapsed-w:76px;--topbar-h:56px;--tabs-h:46px;--analysis-header-h:124px;--sourcebar-h:38px;--filterbar-h:56px;--color-primary:#2100ad;--color-primary-hover:#1a0088;--color-secondary:#6b3fff;--color-accent-soft:#b485ff;--color-violet-pale:#d6ccff;--color-healthcare:#4164ee;--color-discovery:#07816f;--color-pharma:#f7520e;--color-databank:#e04380;--color-healthcare-active:#1a65e0;--color-discovery-active:#066b5c;--color-pharma-active:#d9470c;--color-databank-active:#c73a6f;--healthcare-rgb:65,100,238;--discovery-rgb:7,129,111;--pharma-rgb:247,82,14;--databank-rgb:224,67,128;--color-accent-teal:#00d4a8;--color-notification:#e59;--color-text-inverse:#fff;--color-success:#16a34a;--color-success-bg:#dcfce7;--color-warning:#d97706;--color-warning-bg:#fef3c7;--color-danger:#dc2626;--color-danger-bg:#fee2e2;--color-info:#4164ee;--color-info-bg:#dbeafe;--bg-app:#eef1f5;--bg-surface:#fff;--bg-surface-2:#fbfbfd;--bg-surface-3:#f5f5f9;--bg-hover:#eef1fe;--border-default:#dbdef0;--border-soft:#f0f1f7;--text-primary:#1e293b;--text-secondary:#525670;--text-muted:#878ca6;--brand-rgb:33,0,173;--chart-cat-1:#2100ad;--chart-cat-2:#00a38e;--chart-cat-3:#e67e22;--chart-cat-4:#d63384;--chart-cat-5:#4164ee;--chart-cat-6:#16a34a;--space-2:2px;--space-4:4px;--space-6:6px;--space-8:8px;--space-12:12px;--space-16:16px;--space-20:20px;--space-24:24px;--space-32:32px;--space-40:40px;--space-48:48px;--radius-sm:4px;--radius-md:6px;--radius-lg:8px;--radius-xl:10px;--radius-2xl:12px;--radius-pill:999px;--font-10:10px;--font-11:11px;--font-12:12px;--font-13:13px;--font-14:14px;--font-16:16px;--font-18:18px;--font-22:22px;--font-24:24px;--font-32:32px;--weight-normal:400;--weight-medium:500;--weight-semibold:600;--weight-bold:700;--font-serif:"Caudex",Georgia,serif;--font-sans:"Figtree",system-ui,sans-serif;--font-mono:"SF Mono",Menlo,monospace;--shadow-xs:0 1px 2px rgba(30,41,59,.04);--shadow-sm:0 2px 6px rgba(30,41,59,.06),0 1px 2px rgba(30,41,59,.03);--shadow-md:0 4px 12px rgba(30,41,59,.06),0 1px 3px rgba(30,41,59,.04);--shadow-lg:0 8px 24px rgba(30,41,59,.12);--shadow-focus:0 0 0 3px rgba(107,63,255,.14);--duration-instant:100ms;--duration-fast:150ms;--duration-base:240ms;--duration-slow:400ms;--duration-slowest:600ms;--ease-out:cubic-bezier(.2,0,0,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--ease-spring:cubic-bezier(.5,1.6,.4,1);--z-base:1;--z-sticky:20;--z-header:30;--z-drawer:40;--z-modal:50;--z-toast:60;--z-help:70}body.night{--color-primary:#9b7cff;--color-secondary:#b485ff;--color-accent-soft:#d6ccff;--color-databank:#f472b6;--bg-app:#0e0e14;--bg-surface:#1a1a24;--bg-surface-2:#232330;--bg-surface-3:#2a2a38;--bg-hover:#2d2a42;--border-default:#45455c;--border-soft:#32323f;--text-primary:#f5f6fa;--text-secondary:#cfd3e5;--text-muted:#8a90a8;--brand-rgb:155,124,255;--color-success:#6fe8c5;--color-success-bg:rgba(0,212,168,.18);--color-warning:#fbbf24;--color-warning-bg:rgba(245,158,11,.18);--color-danger:#fca5a5;--color-danger-bg:rgba(239,68,68,.18);--color-info:#7aa0ff;--color-info-bg:rgba(65,100,238,.18);--chart-cat-1:#9b7cff;--chart-cat-2:#6fe8c5;--chart-cat-3:#fbbf24;--chart-cat-4:#f472b6;--chart-cat-5:#7b9ef5;--chart-cat-6:#bef264;background:var(--bg-app);color:var(--text-primary)}body.high-contrast:not(.night){--text-primary:#000;--text-secondary:#1a1a24;--text-muted:#2f2f40;--border-default:#000;--border-soft:#525670;--bg-app:#fff;--bg-surface:#fff;--bg-surface-2:#fff;--bg-surface-3:#f0f1f7;--bg-hover:#ffe56b;--color-primary:#1100a0;--color-primary-hover:#0a0070;--color-secondary:#5028d8;--color-success:#006b2c;--color-warning:#8c4500;--color-danger:#a60000;--color-info:#0040c8}body.night.high-contrast{--text-primary:#fff;--text-secondary:#f5f6fa;--text-muted:#cfd3e5;--border-default:#fff;--border-soft:#b8bcd0;--bg-app:#000;--bg-surface:#000;--bg-surface-2:#000;--bg-surface-3:#000;--bg-hover:#ffe56b;--color-primary:#d6ccff;--color-primary-hover:#fff;--color-secondary:#e5daff;--color-accent-soft:#fff;--color-success:#6fe8c5;--color-warning:#fbbf24;--color-danger:#fca5a5;--color-info:#7aa0ff;--brand-rgb:214,204,255}body.reduce-motion *,body.reduce-motion :after,body.reduce-motion :before{animation:none!important;scroll-behavior:auto!important;transition:none!important}:root{--r-color-primary-50:#f0f4ff;--r-color-primary-100:#dce5fd;--r-color-primary-200:#b9cbfb;--r-color-primary-300:#96b0f9;--r-color-primary-400:#7395f5;--r-color-primary-500:#5a7ef2;--r-color-primary-600:var(--color-healthcare);--r-color-primary-700:#3451d4;--r-color-primary-800:#283fb0;--r-color-primary-900:#1c2d8a;--r-color-gray-50:#f9fafb;--r-color-gray-100:#f3f4f6;--r-color-gray-200:#e5e7eb;--r-color-gray-300:#d1d5db;--r-color-gray-400:#9ca3af;--r-color-gray-500:#6b7280;--r-color-gray-600:#4b5563;--r-color-gray-700:#374151;--r-color-gray-800:#1f2937;--r-color-gray-900:#111827;--r-color-gray-950:#030712;--r-color-success-500:var(--color-success);--r-color-success-600:#16a34a;--r-color-warning-500:#f59e0b;--r-color-warning-600:var(--color-warning);--r-color-danger-500:#ef4444;--r-color-danger-600:var(--color-danger);--r-color-white:#fff;--r-color-black:#000;--r-font-family:var(--font-sans);--r-font-mono:var(--font-mono);--r-text-xs:0.75rem;--r-text-sm:0.875rem;--r-text-base:1rem;--r-text-lg:1.125rem;--r-text-xl:1.25rem;--r-text-2xl:1.5rem;--r-text-3xl:1.875rem;--r-text-4xl:2.25rem;--r-font-weight-normal:var(--weight-normal);--r-font-weight-medium:var(--weight-medium);--r-font-weight-semibold:var(--weight-semibold);--r-font-weight-bold:var(--weight-bold);--r-leading-tight:1.25;--r-leading-normal:1.5;--r-leading-relaxed:1.75;--r-space-0:0;--r-space-1:var(--space-4);--r-space-2:var(--space-8);--r-space-3:var(--space-12);--r-space-4:var(--space-16);--r-space-5:var(--space-20);--r-space-6:var(--space-24);--r-space-8:var(--space-32);--r-space-10:var(--space-40);--r-space-12:var(--space-48);--r-radius-none:0;--r-radius-sm:var(--radius-sm);--r-radius-md:var(--radius-md);--r-radius-lg:var(--radius-lg);--r-radius-xl:0.75rem;--r-radius-2xl:1rem;--r-radius-full:var(--radius-pill);--r-shadow-xs:0 1px 2px 0 rgba(0,0,0,.05);--r-shadow-sm:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--r-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--r-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--r-shadow-xl:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--r-transition-fast:var(--duration-fast) var(--ease-in-out);--r-transition-base:200ms var(--ease-in-out);--r-transition-slow:var(--duration-slow) var(--ease-in-out)}
@@ -0,0 +1 @@
1
+ .row{display:flex;flex-direction:row}.column{display:flex;flex-direction:column}.row-reverse{display:flex;flex-direction:row-reverse}.column-reverse{display:flex;flex-direction:column-reverse}.x-start{justify-content:flex-start}.x-center{justify-content:center}.x-end{justify-content:flex-end}.x-between{justify-content:space-between}.x-around{justify-content:space-around}.x-evenly{justify-content:space-evenly}.y-start{align-items:flex-start}.y-center{align-items:center}.y-end{align-items:flex-end}.y-between{align-content:space-between}.y-stretch{align-items:stretch}.y-baseline{align-items:baseline}.wrap{flex-wrap:wrap}.nowrap{flex-wrap:nowrap}.grow{flex-grow:1}.grow-0{flex-grow:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-1{flex:1}.flex-auto{flex:auto}.flex-none{flex:none}.self-start{align-self:flex-start}.self-center{align-self:center}.self-end{align-self:flex-end}.self-stretch{align-self:stretch}.w-full{width:100%}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.min-w-0{min-width:0}.h-full{height:100%}.h-auto{height:auto}.h-screen{height:100vh}.min-h-0{min-height:0}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.sticky{position:sticky}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.block{display:block}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.gap-4{gap:4px}.gap-6{gap:6px}.gap-8{gap:8px}.gap-10{gap:10px}.gap-12{gap:12px}.gap-14{gap:14px}.gap-16{gap:16px}.gap-18{gap:18px}.gap-20{gap:20px}.gap-22{gap:22px}.gap-24{gap:24px}.gap-26{gap:26px}.gap-28{gap:28px}.gap-30{gap:30px}.gap-32{gap:32px}.gap-34{gap:34px}.gap-36{gap:36px}.gap-38{gap:38px}.gap-40{gap:40px}.gap-42{gap:42px}.gap-44{gap:44px}.gap-46{gap:46px}.gap-48{gap:48px}.gap-50{gap:50px}.gap-52{gap:52px}.gap-54{gap:54px}.gap-56{gap:56px}.gap-58{gap:58px}.gap-60{gap:60px}.gap-62{gap:62px}.gap-64{gap:64px}.gap-66{gap:66px}.gap-68{gap:68px}.gap-70{gap:70px}.gap-72{gap:72px}.gap-74{gap:74px}.gap-76{gap:76px}.gap-78{gap:78px}.gap-80{gap:80px}.p-0{padding:0}.p-4{padding:4px}.p-8{padding:8px}.p-12{padding:12px}.p-16{padding:16px}.p-20{padding:20px}.p-24{padding:24px}.p-32{padding:32px}.p-40{padding:40px}.p-48{padding:48px}.px-4{padding-left:4px;padding-right:4px}.px-8{padding-left:8px;padding-right:8px}.px-12{padding-left:12px;padding-right:12px}.px-16{padding-left:16px;padding-right:16px}.px-20{padding-left:20px;padding-right:20px}.px-24{padding-left:24px;padding-right:24px}.px-32{padding-left:32px;padding-right:32px}.py-4{padding-bottom:4px;padding-top:4px}.py-8{padding-bottom:8px;padding-top:8px}.py-12{padding-bottom:12px;padding-top:12px}.py-16{padding-bottom:16px;padding-top:16px}.py-20{padding-bottom:20px;padding-top:20px}.py-24{padding-bottom:24px;padding-top:24px}.py-32{padding-bottom:32px;padding-top:32px}.m-0{margin:0}.m-auto{margin:auto}.mx-auto{margin-right:auto}.ml-auto,.mx-auto{margin-left:auto}.mr-auto{margin-right:auto}.mt-auto{margin-top:auto}.mb-auto{margin-bottom:auto}.border{border:1px solid var(--r-color-gray-200)}.border-t{border-top:1px solid var(--r-color-gray-200)}.border-b{border-bottom:1px solid var(--r-color-gray-200)}.border-l{border-left:1px solid var(--r-color-gray-200)}.border-r{border-right:1px solid var(--r-color-gray-200)}.border-none{border:none}.rounded-sm{border-radius:var(--r-radius-sm)}.rounded-md{border-radius:var(--r-radius-md)}.rounded-lg{border-radius:var(--r-radius-lg)}.rounded-xl{border-radius:var(--r-radius-xl)}.rounded-full{border-radius:var(--r-radius-full)}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}
@@ -0,0 +1,95 @@
1
+ <script setup lang="ts">
2
+ export interface RmActionBarProps {
3
+ /** Width of the action panel in px */
4
+ panelWidth?: number
5
+ /** Width of the drawer when open in px */
6
+ drawerWidth?: number
7
+ /** Whether the drawer is open */
8
+ drawerOpen?: boolean
9
+ }
10
+
11
+ const props = withDefaults(defineProps<RmActionBarProps>(), {
12
+ panelWidth: 280,
13
+ drawerWidth: 640,
14
+ drawerOpen: false,
15
+ })
16
+
17
+ const emit = defineEmits<{
18
+ 'update:drawerOpen': [value: boolean]
19
+ 'closeDrawer': []
20
+ }>()
21
+
22
+ const totalWidth = computed(() => {
23
+ return props.drawerOpen
24
+ ? props.panelWidth + props.drawerWidth
25
+ : props.panelWidth
26
+ })
27
+
28
+ function closeDrawer() {
29
+ emit('update:drawerOpen', false)
30
+ emit('closeDrawer')
31
+ }
32
+ </script>
33
+
34
+
35
+ <template>
36
+ <div
37
+ class="rm-action-bar"
38
+ :style="{ width: totalWidth + 'px' }"
39
+ >
40
+ <!-- Panel -->
41
+ <div class="rm-action-bar__panel" :style="{ width: panelWidth + 'px' }">
42
+ <div class="rm-action-bar__header">
43
+ <slot name="header" />
44
+ </div>
45
+
46
+ <div v-if="$slots.tabs" class="rm-action-bar__tabs">
47
+ <slot name="tabs" />
48
+ </div>
49
+
50
+ <div class="rm-action-bar__content">
51
+ <slot name="actions" />
52
+ </div>
53
+
54
+ <div v-if="$slots.footer" class="rm-action-bar__footer">
55
+ <slot name="footer" />
56
+ </div>
57
+ </div>
58
+
59
+ <!-- Overlay -->
60
+ <Teleport to="body">
61
+ <Transition name="rm-action-bar-overlay">
62
+ <div
63
+ v-if="drawerOpen"
64
+ class="rm-action-bar__overlay"
65
+ @click="closeDrawer"
66
+ />
67
+ </Transition>
68
+ </Teleport>
69
+
70
+ <!-- Drawer -->
71
+ <Transition name="rm-action-bar-drawer">
72
+ <div
73
+ v-if="drawerOpen"
74
+ class="rm-action-bar__drawer"
75
+ >
76
+ <div class="rm-action-bar__drawer-header">
77
+ <slot name="drawer-header" />
78
+ <button class="rm-action-bar__drawer-close" @click="closeDrawer">
79
+ <RmIcon name="X" :size="20" />
80
+ </button>
81
+ </div>
82
+ <div class="rm-action-bar__drawer-body">
83
+ <slot name="drawer" />
84
+ </div>
85
+ <div v-if="$slots['drawer-footer']" class="rm-action-bar__drawer-footer">
86
+ <slot name="drawer-footer" />
87
+ </div>
88
+ </div>
89
+ </Transition>
90
+ </div>
91
+ </template>
92
+
93
+ <style scoped>
94
+ .rm-action-bar{bottom:0;display:flex;position:fixed;right:0;top:0;transition:width .3s cubic-bezier(.4,0,.2,1);z-index:61}.rm-action-bar__panel{background-color:var(--r-color-white);border-left:1px solid var(--r-color-gray-200);display:flex;flex-direction:column;flex-shrink:0;height:100%}.rm-action-bar__header{align-items:center;display:flex;gap:12px;justify-content:space-between;min-height:64px;padding:0 16px}.rm-action-bar__tabs{border-bottom:1px solid var(--r-color-gray-200);padding:8px 16px 12px}.rm-action-bar__content{display:flex;flex:1;flex-direction:column;overflow-y:auto}.rm-action-bar__footer{border-top:1px solid var(--r-color-gray-200);padding:16px}.rm-action-bar__overlay{backdrop-filter:blur(2px);background-color:rgba(0,0,0,.2);inset:0;position:fixed;z-index:60}.rm-action-bar-overlay-enter-active,.rm-action-bar-overlay-leave-active{transition:opacity .25s ease}.rm-action-bar-overlay-enter-from,.rm-action-bar-overlay-leave-to{opacity:0}.rm-action-bar__drawer{background-color:var(--r-color-white);border-left:1px solid var(--r-color-gray-200);display:flex;flex:1;flex-direction:column;min-width:0;overflow:hidden}.rm-action-bar__drawer-header{align-items:center;border-bottom:1px solid var(--r-color-gray-200);display:flex;justify-content:space-between;min-height:64px;padding:0 24px}.rm-action-bar__drawer-close{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--r-color-gray-500);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;transition:all var(--r-transition-fast);width:32px}.rm-action-bar__drawer-close:hover{background-color:var(--r-color-gray-100);color:var(--r-color-gray-700)}.rm-action-bar__drawer-body{flex:1;overflow-y:auto;padding:24px}.rm-action-bar__drawer-footer{border-top:1px solid var(--r-color-gray-200);padding:16px 24px}.rm-action-bar-drawer-enter-active,.rm-action-bar-drawer-leave-active{transition:opacity .25s ease}.rm-action-bar-drawer-enter-from,.rm-action-bar-drawer-leave-to{opacity:0}
95
+ </style>
@@ -0,0 +1,110 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ PhTrash,
4
+ PhPlus,
5
+ PhLineVertical,
6
+ PhMinus,
7
+ } from '@phosphor-icons/vue'
8
+
9
+ interface SearchRow {
10
+ id: number
11
+ type: 'and' | 'or' | 'not'
12
+ value: string
13
+ }
14
+
15
+ defineProps<{
16
+ visible: boolean
17
+ }>()
18
+
19
+ defineEmits<{
20
+ close: []
21
+ saveSearch: []
22
+ }>()
23
+
24
+ let nextId = 1
25
+
26
+ const rows = ref<SearchRow[]>([
27
+ { id: nextId++, type: 'and', value: '' },
28
+ ])
29
+
30
+ function addRow(type: 'and' | 'or' | 'not') {
31
+ rows.value.push({ id: nextId++, type, value: '' })
32
+ }
33
+
34
+ function removeRow(id: number) {
35
+ rows.value = rows.value.filter(r => r.id !== id)
36
+ if (rows.value.length === 0) {
37
+ rows.value.push({ id: nextId++, type: 'and', value: '' })
38
+ }
39
+ }
40
+
41
+ function clearAll() {
42
+ rows.value = [{ id: nextId++, type: 'and', value: '' }]
43
+ }
44
+
45
+ const typeColors: Record<string, string> = {
46
+ and: '#4164EE',
47
+ or: '#07816F',
48
+ not: '#F7520E',
49
+ }
50
+ </script>
51
+
52
+ <template>
53
+ <div v-if="visible" class="advanced-search">
54
+ <div class="advanced-search__header">
55
+ <h2 class="advanced-search__title">Advanced search</h2>
56
+ <RmButton variant="outline" size="sm" @click="$emit('saveSearch')">
57
+ Save search
58
+ <PhLineVertical :size="14" />
59
+ </RmButton>
60
+ </div>
61
+
62
+ <div class="advanced-search__rows">
63
+ <div
64
+ v-for="row in rows"
65
+ :key="row.id"
66
+ class="advanced-search__row"
67
+ >
68
+ <span
69
+ class="advanced-search__type"
70
+ :style="{ color: typeColors[row.type] }"
71
+ >
72
+ {{ row.type.charAt(0).toUpperCase() + row.type.slice(1) }}
73
+ </span>
74
+ <input
75
+ v-model="row.value"
76
+ type="text"
77
+ :placeholder="row.value ? '' : 'Add a keyword'"
78
+ class="advanced-search__input"
79
+ :class="{ 'advanced-search__input--not': row.type === 'not' }"
80
+ >
81
+ <button class="advanced-search__delete" @click="removeRow(row.id)">
82
+ <PhTrash :size="16" />
83
+ </button>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="advanced-search__actions">
88
+ <button class="advanced-search__add" @click="addRow('and')">
89
+ <PhPlus :size="12" />
90
+ and
91
+ </button>
92
+ <button class="advanced-search__add" @click="addRow('or')">
93
+ <PhLineVertical :size="12" />
94
+ or
95
+ </button>
96
+ <button class="advanced-search__add" @click="addRow('not')">
97
+ <PhMinus :size="12" />
98
+ not
99
+ </button>
100
+ </div>
101
+
102
+ <button class="advanced-search__clear" @click="clearAll">
103
+ Clear All
104
+ </button>
105
+ </div>
106
+ </template>
107
+
108
+ <style scoped>
109
+ .advanced-search{background-color:var(--r-color-white);border-bottom:1px solid var(--r-color-gray-200);padding:20px 24px}.advanced-search__header{align-items:center;display:flex;justify-content:space-between;margin-bottom:20px}.advanced-search__title{color:var(--r-color-gray-900);font-family:var(--r-font-family);font-size:16px;font-weight:700;margin:0}.advanced-search__rows{display:flex;flex-direction:column;gap:12px;margin-bottom:16px}.advanced-search__row{align-items:center;display:flex;gap:12px}.advanced-search__type{flex-shrink:0;font-weight:600;min-width:32px}.advanced-search__input,.advanced-search__type{font-family:var(--r-font-family);font-size:var(--r-text-sm)}.advanced-search__input{background:#fff;border:1px solid #dbdef0;border-radius:6px;color:var(--r-color-gray-900);flex:1;height:36px;outline:none;padding:0 12px;transition:border-color var(--r-transition-fast)}.advanced-search__input:focus{border-color:#4164ee}.advanced-search__input--not,.advanced-search__input--not:focus{border-color:#f7520e}.advanced-search__input::-moz-placeholder{color:var(--r-color-gray-400)}.advanced-search__input::placeholder{color:var(--r-color-gray-400)}.advanced-search__delete{align-items:center;background:none;border:none;border-radius:4px;color:var(--r-color-gray-400);cursor:pointer;display:flex;justify-content:center;padding:4px;transition:color var(--r-transition-fast)}.advanced-search__delete:hover{color:var(--r-color-danger-500)}.advanced-search__actions{display:flex;gap:8px;margin-bottom:16px}.advanced-search__add{align-items:center;background:#fff;border:1px solid #dbdef0;border-radius:6px;color:var(--r-color-gray-600);cursor:pointer;display:inline-flex;font-family:var(--r-font-family);font-size:var(--r-text-sm);font-weight:var(--r-font-weight-medium);gap:4px;height:32px;padding:4px 12px;transition:all var(--r-transition-fast)}.advanced-search__add:hover{border-color:var(--r-color-gray-300);box-shadow:0 1px 2px 0 rgba(30,41,59,.1)}.advanced-search__clear{background:none;border:1px solid var(--r-color-danger-500);border-radius:6px;color:var(--r-color-danger-500);cursor:pointer;font-family:var(--r-font-family);font-size:var(--r-text-sm);font-weight:var(--r-font-weight-medium);padding:4px 12px;transition:all var(--r-transition-fast)}.advanced-search__clear:hover{background-color:var(--r-color-danger-500);color:#fff}
110
+ </style>
@@ -0,0 +1,15 @@
1
+ <!--
2
+ RmBadge - label de statut STATIQUE (non cliquable).
3
+ Style : components.css (.badge). Jamais la couleur seule : le badge porte
4
+ toujours son texte. Pour un filtre cliquable, ce n'est pas un badge.
5
+ -->
6
+ <script setup lang="ts">
7
+ withDefaults(defineProps<{
8
+ /** active = favorable/inscrit/valide · pending = en cours/restreint · error = radie/refus · info · grey = neutre */
9
+ variant?: 'active' | 'pending' | 'error' | 'info' | 'grey'
10
+ }>(), { variant: 'grey' })
11
+ </script>
12
+
13
+ <template>
14
+ <span class="badge" :class="`badge-${variant}`"><slot /></span>
15
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ export interface RmBreadcrumbItem {
3
+ /** Display label */
4
+ label: string
5
+ /** Optional icon name (Phosphor) — only shown on first item by default */
6
+ icon?: string
7
+ /** Navigation path */
8
+ to?: string
9
+ }
10
+
11
+ export interface RmBreadcrumbProps {
12
+ /** Breadcrumb items */
13
+ items: RmBreadcrumbItem[]
14
+ }
15
+
16
+ defineProps<RmBreadcrumbProps>()
17
+ </script>
18
+
19
+ <template>
20
+ <nav class="rm-breadcrumb">
21
+ <template v-for="(item, idx) in items" :key="idx">
22
+ <span v-if="idx > 0" class="rm-breadcrumb__sep">/</span>
23
+ <NuxtLink
24
+ v-if="item.to"
25
+ :to="item.to"
26
+ class="rm-breadcrumb__item"
27
+ :class="{ 'rm-breadcrumb__item--first': idx === 0 }"
28
+ >
29
+ <RmIcon v-if="item.icon" :name="item.icon" :size="16" weight="duotone" class="rm-breadcrumb__icon" />
30
+ {{ item.label }}
31
+ </NuxtLink>
32
+ <span
33
+ v-else
34
+ class="rm-breadcrumb__item"
35
+ :class="{ 'rm-breadcrumb__item--first': idx === 0 }"
36
+ >
37
+ <RmIcon v-if="item.icon" :name="item.icon" :size="16" weight="duotone" class="rm-breadcrumb__icon" />
38
+ {{ item.label }}
39
+ </span>
40
+ </template>
41
+ </nav>
42
+ </template>
43
+
44
+ <style scoped>
45
+ .rm-breadcrumb{align-items:center;color:var(--r-color-gray-400);display:flex;font-family:var(--r-font-family);font-size:14px;gap:8px}.rm-breadcrumb__sep{color:var(--r-color-gray-300)}.rm-breadcrumb__item{align-items:center;color:var(--r-color-gray-400);display:inline-flex;gap:6px;text-decoration:none;transition:color var(--r-transition-fast)}.rm-breadcrumb__item--first{color:var(--r-color-gray-700);font-weight:500}a.rm-breadcrumb__item:hover{color:var(--r-color-primary-600)}.rm-breadcrumb__icon{color:var(--r-color-primary-500)}
46
+ </style>
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
- export interface RButtonProps {
2
+ export interface RmButtonProps {
3
3
  /** Visual style variant */
4
- variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
4
+ variant?: 'primary-fill' | 'primary-stroke' | 'discovery-fill' | 'discovery-stroke' | 'databank-fill' | 'databank-stroke' | 'pharma-fill' | 'pharma-stroke' | 'secondary' | 'outline' | 'ghost'
5
5
  /** Size of the button */
6
6
  size?: 'sm' | 'md' | 'lg'
7
7
  /** Whether the button is disabled */
@@ -10,14 +10,20 @@ export interface RButtonProps {
10
10
  block?: boolean
11
11
  /** HTML button type attribute */
12
12
  type?: 'button' | 'submit' | 'reset'
13
+ /** Phosphor icon name before the label */
14
+ prefixIcon?: string
15
+ /** Phosphor icon name after the label */
16
+ suffixIcon?: string
13
17
  }
14
18
 
15
- withDefaults(defineProps<RButtonProps>(), {
16
- variant: 'primary',
19
+ withDefaults(defineProps<RmButtonProps>(), {
20
+ variant: 'primary-fill',
17
21
  size: 'md',
18
22
  disabled: false,
19
23
  block: false,
20
24
  type: 'button',
25
+ prefixIcon: undefined,
26
+ suffixIcon: undefined,
21
27
  })
22
28
 
23
29
  defineEmits<{
@@ -30,17 +36,19 @@ defineEmits<{
30
36
  :type="type"
31
37
  :disabled="disabled"
32
38
  :class="[
33
- 'r-button',
34
- `r-button--${variant}`,
35
- `r-button--${size}`,
36
- { 'r-button--block': block },
39
+ 'rm-button',
40
+ `rm-button--${variant}`,
41
+ `rm-button--${size}`,
42
+ { 'rm-button--block': block },
37
43
  ]"
38
44
  @click="$emit('click', $event)"
39
45
  >
46
+ <RmIcon v-if="prefixIcon" :name="prefixIcon" :size="size === 'sm' ? 14 : 16" />
40
47
  <slot />
48
+ <RmIcon v-if="suffixIcon" :name="suffixIcon" :size="size === 'sm' ? 14 : 16" />
41
49
  </button>
42
50
  </template>
43
51
 
44
52
  <style scoped>
45
- .r-button{align-items:center;border:1px solid transparent;border-radius:var(--r-radius-lg);cursor:pointer;display:inline-flex;font-weight:var(--r-font-weight-medium);gap:var(--r-space-2);justify-content:center;transition:all var(--r-transition-fast);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.r-button:focus-visible{outline:2px solid var(--r-color-primary-500);outline-offset:2px}.r-button:disabled{cursor:not-allowed;opacity:.5}.r-button--sm{font-size:var(--r-text-sm);height:2rem;padding:var(--r-space-1) var(--r-space-3)}.r-button--md{font-size:var(--r-text-sm);height:2.5rem;padding:var(--r-space-2) var(--r-space-4)}.r-button--lg{font-size:var(--r-text-base);height:3rem;padding:var(--r-space-3) var(--r-space-6)}.r-button--primary{background-color:var(--r-color-primary-600);color:var(--r-color-white)}.r-button--primary:hover:not(:disabled){background-color:var(--r-color-primary-700)}.r-button--primary:active:not(:disabled){background-color:var(--r-color-primary-800)}.r-button--secondary{background-color:var(--r-color-gray-100);color:var(--r-color-gray-800)}.r-button--secondary:hover:not(:disabled){background-color:var(--r-color-gray-200)}.r-button--secondary:active:not(:disabled){background-color:var(--r-color-gray-300)}.r-button--outline{background-color:transparent;border-color:var(--r-color-gray-300);color:var(--r-color-gray-700)}.r-button--outline:hover:not(:disabled){background-color:var(--r-color-gray-50);border-color:var(--r-color-gray-400)}.r-button--outline:active:not(:disabled){background-color:var(--r-color-gray-100)}.r-button--ghost{background-color:transparent;color:var(--r-color-gray-700)}.r-button--ghost:hover:not(:disabled){background-color:var(--r-color-gray-100)}.r-button--ghost:active:not(:disabled){background-color:var(--r-color-gray-200)}.r-button--block{width:100%}
53
+ .rm-button{align-items:center;border:1px solid transparent;border-radius:6px;cursor:pointer;display:inline-flex;font-family:var(--r-font-family);font-weight:500;gap:8px;justify-content:center;transition:all var(--r-transition-fast);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.rm-button:focus-visible{outline:2px solid var(--r-color-primary-500);outline-offset:2px}.rm-button:disabled{cursor:not-allowed;opacity:.5}.rm-button--sm{font-size:13px;height:32px;padding:6px 16px}.rm-button--md{font-size:14px;height:40px;padding:10px 24px}.rm-button--lg{font-size:15px;height:48px;padding:12px 28px}.rm-button--primary-fill{background:linear-gradient(180deg,rgba(65,100,238,.8),rgba(65,100,238,.9));border-color:#4164ee;box-shadow:0 2px 3px 0 rgba(65,100,238,.5);color:var(--r-color-white)}.rm-button--primary-fill:hover:not(:disabled){background:linear-gradient(180deg,#4164ee,#4164ee);box-shadow:0 2px 4px 0 rgba(65,100,238,.6)}.rm-button--primary-fill:active:not(:disabled){background:#3451d4;box-shadow:0 1px 2px 0 rgba(65,100,238,.4)}.rm-button--primary-stroke{box-shadow:0 2px 3px 0 rgba(65,100,238,.5);color:#4164ee}.rm-button--primary-stroke,.rm-button--primary-stroke:hover:not(:disabled){background:linear-gradient(180deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,.9));border-color:#4164ee}.rm-button--primary-stroke:hover:not(:disabled){box-shadow:none}.rm-button--primary-stroke:active:not(:disabled){background:var(--r-color-primary-50);box-shadow:none}.rm-button--discovery-fill{background:linear-gradient(180deg,rgba(7,129,111,.8),rgba(7,129,111,.9));border-color:#07816f;box-shadow:0 2px 3px 0 rgba(7,129,111,.5);color:var(--r-color-white)}.rm-button--discovery-fill:hover:not(:disabled){background:linear-gradient(180deg,#07816f,#07816f);border-color:#07816f;box-shadow:none}.rm-button--discovery-fill:active:not(:disabled){background:linear-gradient(180deg,#066b5c,#066b5c);box-shadow:none}.rm-button--discovery-stroke{box-shadow:0 2px 3px 0 rgba(7,129,111,.5);color:#07816f}.rm-button--discovery-stroke,.rm-button--discovery-stroke:hover:not(:disabled){background:linear-gradient(180deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,.9));border-color:#07816f}.rm-button--discovery-stroke:hover:not(:disabled){box-shadow:none}.rm-button--discovery-stroke:active:not(:disabled){background:rgba(7,129,111,.05);box-shadow:none}.rm-button--databank-fill{background:linear-gradient(180deg,rgba(224,67,128,.8),rgba(224,67,128,.9));border-color:#e04380;box-shadow:0 2px 3px 0 rgba(224,67,128,.5);color:var(--r-color-white)}.rm-button--databank-fill:hover:not(:disabled){background:linear-gradient(180deg,#e04380,#e04380);border-color:#e04380;box-shadow:none}.rm-button--databank-fill:active:not(:disabled){background:linear-gradient(180deg,#c73a6f,#c73a6f);box-shadow:none}.rm-button--databank-stroke{box-shadow:0 2px 3px 0 rgba(224,67,128,.5);color:#e04380}.rm-button--databank-stroke,.rm-button--databank-stroke:hover:not(:disabled){background:linear-gradient(180deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,.9));border-color:#e04380}.rm-button--databank-stroke:hover:not(:disabled){box-shadow:none}.rm-button--databank-stroke:active:not(:disabled){background:rgba(224,67,128,.05);box-shadow:none}.rm-button--pharma-fill{background:linear-gradient(180deg,#ff7942,#f7520e);border-color:#f7520e;box-shadow:0 2px 3px 0 rgba(247,82,14,.5);color:var(--r-color-white)}.rm-button--pharma-fill:hover:not(:disabled){background:linear-gradient(180deg,#f7520e,#f7520e);border-color:#f7520e;box-shadow:none}.rm-button--pharma-fill:active:not(:disabled){background:linear-gradient(180deg,#d9470c,#d9470c);box-shadow:none}.rm-button--pharma-stroke{box-shadow:0 2px 3px 0 rgba(247,82,14,.5);color:#f7520e}.rm-button--pharma-stroke,.rm-button--pharma-stroke:hover:not(:disabled){background:linear-gradient(180deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,.9));border-color:#f7520e}.rm-button--pharma-stroke:hover:not(:disabled){box-shadow:none}.rm-button--pharma-stroke:active:not(:disabled){background:rgba(247,82,14,.05);box-shadow:none}.rm-button--secondary{background-color:var(--r-color-gray-100);color:var(--r-color-gray-800)}.rm-button--secondary:hover:not(:disabled){background-color:var(--r-color-gray-200)}.rm-button--secondary:active:not(:disabled){background-color:var(--r-color-gray-300)}.rm-button--outline{background-color:transparent;border-color:var(--r-color-gray-300);color:var(--r-color-gray-700)}.rm-button--outline:hover:not(:disabled){background-color:var(--r-color-gray-50);border-color:var(--r-color-gray-400)}.rm-button--outline:active:not(:disabled){background-color:var(--r-color-gray-100)}.rm-button--ghost{background-color:transparent;color:var(--r-color-gray-700)}.rm-button--ghost:hover:not(:disabled){background-color:var(--r-color-gray-100)}.rm-button--ghost:active:not(:disabled){background-color:var(--r-color-gray-200)}.rm-button--block{width:100%}
46
54
  </style>
@@ -0,0 +1,129 @@
1
+ <!--
2
+ RmChartCard - LE seul container de donnees autorise (chart, table, groupe
3
+ de KPI). Style : components.css (.chart-card*).
4
+
5
+ Le foot n'est pas une option decorative : c'est la premiere regle Critical
6
+ du design system. Toute donnee affichee porte l'organisme source ET la date
7
+ de notre verification. D'ou `source` et `verified` en props REQUISES : on ne
8
+ peut pas produire une carte sans dire d'ou vient la donnee ni quand on l'a
9
+ controlee. La date de la DONNEE (millesime, periode) vit dans le graphe ou
10
+ le tableau, elle ne se confond jamais avec la date de verification.
11
+ -->
12
+ <script setup lang="ts">
13
+ import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
14
+ import { rmDate } from '../utils/format'
15
+
16
+ const props = withDefaults(defineProps<{
17
+ /** Titre de la carte. */
18
+ title: string
19
+ /** Sous-titre : perimetre, methode, aide a la lecture. */
20
+ context?: string
21
+ /** Organisme public : "HAS", "OCDE", "ANSM (Repertoire des specialites)". Jamais un outil interne. */
22
+ source: string
23
+ /** Date de notre verification. Un Date est formate en francais long ; une chaine deja formatee passe telle quelle. */
24
+ verified: Date | string | number
25
+ /** Complement de fin de foot : frequence, millesime. */
26
+ extra?: string
27
+ /** Masque le menu d'actions quand la carte n'a rien a exporter. */
28
+ actions?: boolean
29
+ }>(), {
30
+ context: undefined,
31
+ extra: undefined,
32
+ actions: true,
33
+ })
34
+
35
+ const emit = defineEmits<{
36
+ exportPng: []
37
+ exportCsv: []
38
+ exportXlsx: []
39
+ copyLink: []
40
+ fullscreen: []
41
+ }>()
42
+
43
+ const menuOpen = ref(false)
44
+ const root = ref<HTMLElement | null>(null)
45
+
46
+ // Une chaine deja formatee ("17 juin 2026") n'est pas parsable : on la garde
47
+ // telle quelle plutot que d'afficher "Invalid Date".
48
+ const verifiedText = computed(() => {
49
+ const parsed = new Date(props.verified)
50
+ return Number.isNaN(parsed.getTime()) ? String(props.verified) : rmDate(parsed)
51
+ })
52
+
53
+ function onDocumentClick(event: MouseEvent) {
54
+ if (!menuOpen.value) return
55
+ if (root.value && !root.value.contains(event.target as Node)) menuOpen.value = false
56
+ }
57
+ function onEscape(event: KeyboardEvent) {
58
+ if (event.key === 'Escape') menuOpen.value = false
59
+ }
60
+
61
+ onMounted(() => {
62
+ document.addEventListener('click', onDocumentClick)
63
+ document.addEventListener('keydown', onEscape)
64
+ })
65
+ onBeforeUnmount(() => {
66
+ document.removeEventListener('click', onDocumentClick)
67
+ document.removeEventListener('keydown', onEscape)
68
+ })
69
+
70
+ function run(action: 'exportPng' | 'exportCsv' | 'exportXlsx' | 'copyLink' | 'fullscreen') {
71
+ menuOpen.value = false
72
+ emit(action)
73
+ }
74
+ </script>
75
+
76
+ <template>
77
+ <div class="chart-card">
78
+ <div class="chart-card-head">
79
+ <p class="chart-card-title">{{ title }}</p>
80
+ <p v-if="context" class="chart-card-context">{{ context }}</p>
81
+
82
+ <div v-if="actions" ref="root" class="chart-card-actions" :class="{ open: menuOpen }">
83
+ <button
84
+ class="cc-export"
85
+ type="button"
86
+ aria-label="Actions sur cette carte"
87
+ :aria-expanded="menuOpen"
88
+ @click.stop="menuOpen = !menuOpen"
89
+ >
90
+ <RmIcon name="Gear" :size="14" />
91
+ </button>
92
+ <div class="cc-menu" role="menu">
93
+ <div class="cc-menu-section">Exporter</div>
94
+ <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportPng')">
95
+ <RmIcon name="Image" :size="14" /> Image PNG
96
+ </button>
97
+ <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportCsv')">
98
+ <RmIcon name="FileCsv" :size="14" /> Données CSV
99
+ </button>
100
+ <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportXlsx')">
101
+ <RmIcon name="FileXls" :size="14" /> Excel (XLSX)
102
+ </button>
103
+ <div class="cc-menu-divider" />
104
+ <div class="cc-menu-section">Partager</div>
105
+ <button class="cc-menu-item" type="button" role="menuitem" @click="run('copyLink')">
106
+ <RmIcon name="Link" :size="14" /> Copier le lien direct
107
+ </button>
108
+ <button class="cc-menu-item" type="button" role="menuitem" @click="run('fullscreen')">
109
+ <RmIcon name="ArrowsOut" :size="14" /> Plein écran
110
+ </button>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="chart-card-body">
116
+ <slot />
117
+ </div>
118
+
119
+ <div class="chart-card-foot">
120
+ <span class="src">Source : <b>{{ source }}</b></span>
121
+ <span class="dot" />
122
+ <span class="updated">Vérifié le {{ verifiedText }}</span>
123
+ <template v-if="extra">
124
+ <span class="dot" />
125
+ <span>{{ extra }}</span>
126
+ </template>
127
+ </div>
128
+ </div>
129
+ </template>