@tekkare/romulus 0.1.37 → 0.1.39

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.36",
7
+ "version": "0.1.38",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -1 +1 @@
1
- :root{--rm-shell-bg:#f9f9fa;--rm-shell-surface:#fff;--rm-shell-border:#dbdef0;--rm-shell-subtle:#f5f5f9;--rm-shell-text:#1e293b;--rm-shell-text-dark:#475569;--rm-shell-text-muted:#878ca6;--rm-shell-accent:#2017ad;--rm-shell-inset:8px;--rm-shell-radius:12px;--rm-bar-h:56px;--rm-nav-w:240px;--rm-nav-collapsed-w:64px;--rm-rail-w:56px;--rm-dots-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAcUlEQVR42u3TwQmAMAxAUdfQfUoSZ3OBTNMFHEqLVBEtCB6M8B8EevwktOsAAACAl0SkNzNXtaw6zut7SikNYeJq2Hksh4gs27rG7ZH+eWA7bttigMC78x4TIdBbceX8fJKnkSL1s5RY8zBxAAAA+LUF0ula10BxVDoAAAAASUVORK5CYII=");--rm-dots-size:12px;--rm-dots-opacity:0.1}body.night{--rm-shell-bg:var(--bg-app);--rm-shell-surface:var(--bg-surface,#1a1a24);--rm-shell-border:var(--border-default);--rm-shell-subtle:var(--bg-subtle,#22222e);--rm-shell-text:var(--text-primary);--rm-shell-text-dark:var(--text-secondary);--rm-shell-text-muted:var(--text-muted);--rm-shell-accent:var(--color-primary);--rm-dots-opacity:0.06}.rm-shell{background:var(--rm-shell-bg);color:var(--rm-shell-text);font-family:var(--font-sans,"Figtree",sans-serif);inset:0;overflow:hidden;position:fixed}.rm-shell__dots{background-image:var(--rm-dots-image);background-repeat:repeat;background-size:var(--rm-dots-size) var(--rm-dots-size);inset:0;opacity:var(--rm-dots-opacity);pointer-events:none;position:absolute}.rm-shell__frame{border:1px solid var(--rm-shell-border);border-radius:var(--rm-shell-radius);display:flex;inset:var(--rm-shell-inset);overflow:hidden;position:absolute}.rm-shell__body{display:flex;flex:1;flex-direction:column;min-width:0}.rm-shell__main{flex:1;min-width:0;overflow-y:auto}.rm-sb{background:var(--rm-shell-surface);display:flex;flex-direction:column;flex-shrink:0;height:100%}.rm-sb--left{border-right:1px solid var(--rm-shell-border);position:relative;transition:width .2s;width:var(--rm-nav-w);z-index:200}.rm-sb--left.rm-sb--collapsed{width:var(--rm-nav-collapsed-w)}.rm-sb--right{border-left:1px solid var(--rm-shell-border);width:var(--rm-rail-w)}.rm-sb__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-sb--collapsed .rm-sb__header{justify-content:center;padding:0}.rm-sb__logo{align-items:center;color:var(--rm-shell-text);display:flex;flex:1;font-size:20px;font-weight:700;gap:8px;line-height:1.4;min-width:0;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__toggle{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-sb__toggle:hover{color:var(--rm-shell-text)}.rm-sb__toggle svg{display:block;height:100%;width:100%}.rm-sb__nav{display:flex;flex:1;flex-direction:column;gap:16px;overflow-y:auto;padding:16px}.rm-sb--collapsed .rm-sb__nav{padding-left:8px;padding-right:8px}.rm-sb__group{display:flex;flex-direction:column;gap:4px}.rm-sb__label{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);display:flex;font-family:inherit;font-size:10px;font-weight:500;gap:4px;letter-spacing:.025em;line-height:1.6;margin:0;padding:0 2px;text-align:left;text-transform:uppercase;width:100%}button.rm-sb__label{cursor:pointer}button.rm-sb__label:hover{color:var(--rm-shell-text)}.rm-sb__label-text{flex:1;min-width:0}.rm-sb__chev{display:block;flex-shrink:0;height:12px;transition:transform .2s;width:12px}.rm-sb__label[aria-expanded=true] .rm-sb__chev{transform:rotate(90deg)}.rm-sb__rule{background:var(--rm-shell-border);height:1px;margin:4px}.rm-mi{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;gap:8px;height:26px;margin:0;padding:2px;text-align:left;text-decoration:none;transition:background-color .15s;width:100%}.rm-mi:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-mi.rm-mi--active{background:var(--rm-shell-subtle)}.rm-mi.rm-mi--current{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent)}.rm-mi.rm-mi--current .rm-mi__icon{background:var(--rm-shell-accent);color:#fff}.rm-mi.rm-mi--current .rm-mi__icon svg{stroke:#fff}.rm-mi__icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-mi__icon svg{display:block;height:14px;width:14px}.rm-mi__icon i{font-size:14px;line-height:1}.rm-mi__label{color:var(--rm-shell-text);flex:1;font-size:14px;line-height:1.625;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-mi__badge{background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-muted);flex-shrink:0;font-size:9px;font-weight:700;letter-spacing:.025em;padding:2px 4px;text-transform:uppercase}.rm-sb--collapsed .rm-mi{justify-content:center}.rm-sb--collapsed .rm-mi>:not(.rm-mi__icon),.rm-sb--collapsed .rm-sb__sub{display:none}.rm-sb__sub{display:flex;flex-direction:column;gap:2px;padding:2px 0 2px 28px}.rm-sb__sub-item{border-radius:4px;color:var(--rm-shell-text);display:block;font-size:14px;line-height:1.625;overflow:hidden;padding:2px 4px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__sub-item:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-rail__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;cursor:pointer;display:flex;flex-direction:column;font-family:inherit;gap:4px;margin:0;padding:12px 0 8px;text-decoration:none;width:100%}.rm-rail__avatar{align-items:center;border:2px solid var(--rm-shell-surface);border-radius:9999px;color:#fff;display:flex;font-size:14px;height:32px;justify-content:center;transition:opacity .15s;width:32px}.rm-rail__item:hover .rm-rail__avatar{opacity:.8}.rm-rail__tile{align-items:center;background:var(--rm-shell-subtle);border-radius:8px;color:var(--rm-shell-text-dark);display:flex;font-size:16px;height:32px;justify-content:center;transition:background-color .15s,color .15s;width:32px}.rm-rail__item:hover .rm-rail__tile{background:var(--rm-shell-border)}.rm-rail__item.rm-rail__item--active .rm-rail__tile{background:var(--rm-shell-accent);color:#fff}.rm-rail__caption{color:var(--rm-shell-text-muted);font-size:10px;line-height:1;max-width:100%;overflow:hidden;padding:0 2px;text-overflow:ellipsis;transition:color .15s;white-space:nowrap}.rm-rail__item--active .rm-rail__caption,.rm-rail__item:hover .rm-rail__caption{color:var(--rm-shell-text)}.rm-rail__bottom{display:flex;justify-content:center;margin-top:auto;padding-bottom:16px}.rm-lang{background:var(--rm-shell-subtle);border:1px solid var(--rm-shell-accent);border-radius:9999px;display:grid;gap:2px;grid-template-rows:1fr 1fr;padding:3px;position:relative}.rm-lang__thumb{background:var(--rm-shell-surface);border-radius:9999px;box-shadow:0 1px 4px rgba(30,41,59,.16);height:32px;left:3px;position:absolute;top:3px;transition:transform .26s cubic-bezier(.34,1.56,.64,1);width:32px}.rm-lang--second .rm-lang__thumb{transform:translateY(34px)}.rm-lang__option{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:9999px;box-shadow:none;cursor:pointer;display:flex;filter:grayscale(1);font-size:17px;height:32px;justify-content:center;line-height:1;margin:0;opacity:.5;padding:0;position:relative;transition:filter .18s ease,opacity .18s ease,transform .18s ease;width:32px}.rm-lang__option:hover{filter:grayscale(0);opacity:.85}.rm-lang__option.rm-lang__option--active{filter:grayscale(0);opacity:1}.rm-lang__option:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}@media (prefers-reduced-motion:reduce){.rm-lang__option,.rm-lang__thumb{transition:none}}.rm-hd{background:var(--rm-shell-surface);border-bottom:1px solid var(--rm-shell-border);display:flex;flex-direction:column;flex-shrink:0}.rm-hd__row{gap:16px;height:var(--rm-bar-h);padding:0 16px}.rm-hd__crumbs,.rm-hd__row{align-items:center;display:flex}.rm-hd__crumbs{flex:1;font-size:14px;gap:4px;min-width:0}.rm-hd__crumb{align-items:center;color:var(--rm-shell-text-muted);display:inline-flex;gap:6px;text-decoration:none;transition:color .15s;white-space:nowrap}a.rm-hd__crumb:hover{color:var(--rm-shell-text)}.rm-hd__sep{color:var(--rm-shell-text-muted)}.rm-hd__picker{display:inline-flex;min-width:0;position:relative}.rm-hd__crumb--picker{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;cursor:pointer;font:inherit;font-size:14px;margin:0;padding:2px 4px;transition:background-color .15s,color .15s}.rm-hd__crumb--picker:hover{background:var(--rm-shell-subtle)}.rm-hd__caret{display:block;flex-shrink:0;height:12px;opacity:.7;transition:transform .15s;width:12px}.rm-hd__caret--open{transform:rotate(180deg)}.rm-hd__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;box-shadow:0 8px 24px rgba(30,41,59,.12);left:0;max-height:60vh;max-width:320px;min-width:200px;overflow-y:auto;padding:4px;position:absolute;top:calc(100% + 4px);z-index:300}.rm-hd__menu-item{align-items:center;border-radius:4px;color:var(--rm-shell-text);display:flex;font-size:14px;gap:8px;line-height:1.5;overflow:hidden;padding:4px 6px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-hd__menu-item:hover{background:var(--rm-shell-subtle)}.rm-hd__menu-group{color:var(--rm-shell-text-muted);display:block;font-size:10px;font-weight:500;letter-spacing:.025em;line-height:1.6;padding:8px 6px 2px;text-transform:uppercase}.rm-hd__menu-group:first-child{padding-top:2px}.rm-hd__menu-item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-hd__menu-icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-hd__menu-icon i{font-size:14px;line-height:1}.rm-hd__menu-item--active .rm-hd__menu-icon{background:var(--rm-shell-accent);color:#fff}.rm-hd__crumb--last{color:var(--rm-shell-text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-hd__search{align-items:center;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;display:flex;flex-shrink:0;gap:4px;padding:4px 12px;width:300px}.rm-hd__search input{background:transparent;border:none;color:var(--rm-shell-text);flex:1;font-family:inherit;font-size:14px;line-height:20px;min-width:0;outline:none;padding:0}.rm-hd__search input::-moz-placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search input::placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search i,.rm-hd__search svg{color:var(--rm-shell-text-muted);display:block;flex-shrink:0;font-size:14px;height:14px;line-height:20px;width:14px}.rm-hd__tabs{display:flex;gap:16px;padding:0 16px}.rm-hd__tab{align-items:flex-start;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:var(--rm-shell-text-dark);cursor:pointer;display:flex;flex-direction:column;font-family:inherit;font-size:14px;font-weight:700;line-height:1.6;margin:0;padding:4px 4px 8px;white-space:nowrap}.rm-hd__tab--active{border-bottom-color:var(--rm-shell-accent);color:var(--rm-shell-text)}@media (max-width:767px){.rm-sb--left{inset:0 auto 0 0;position:absolute;transform:translateX(-100%);transition:transform .2s;z-index:40}.rm-sb--left.rm-sb--open{transform:translateX(0)}.rm-sb--right{display:none}.rm-hd__search{flex:1;width:auto}}.rm-panel{background:var(--rm-shell-surface);border-left:1px solid var(--rm-shell-border);bottom:0;box-shadow:-8px 0 24px rgba(30,41,59,.08);display:flex;flex-direction:column;position:absolute;right:0;top:0;width:var(--rm-panel-w,360px);z-index:200}.rm-sb--right{position:relative;z-index:201}.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:transform .22s cubic-bezier(.4,0,.2,1)}.rm-panel-enter-from,.rm-panel-leave-to{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:none}}.rm-panel__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-panel__title{color:var(--rm-shell-text);flex:1;font-size:14px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-panel__close{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-panel__close:hover{color:var(--rm-shell-text)}.rm-panel__close svg{display:block;height:100%;width:100%}.rm-panel__body{flex:1;min-height:0;overflow-y:auto;padding:16px}@media (max-width:767px){.rm-panel{border-left:none;left:0;width:auto}}
1
+ :root{--rm-shell-bg:#f9f9fa;--rm-shell-surface:#fff;--rm-shell-border:#dbdef0;--rm-shell-subtle:#f5f5f9;--rm-shell-text:#1e293b;--rm-shell-text-dark:#475569;--rm-shell-text-muted:#878ca6;--rm-shell-accent:#2017ad;--rm-shell-inset:8px;--rm-shell-radius:12px;--rm-bar-h:56px;--rm-nav-w:240px;--rm-nav-collapsed-w:64px;--rm-rail-w:56px;--rm-dots-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAcUlEQVR42u3TwQmAMAxAUdfQfUoSZ3OBTNMFHEqLVBEtCB6M8B8EevwktOsAAACAl0SkNzNXtaw6zut7SikNYeJq2Hksh4gs27rG7ZH+eWA7bttigMC78x4TIdBbceX8fJKnkSL1s5RY8zBxAAAA+LUF0ula10BxVDoAAAAASUVORK5CYII=");--rm-dots-size:12px;--rm-dots-opacity:0.1}body.night{--rm-shell-bg:var(--bg-app);--rm-shell-surface:var(--bg-surface,#1a1a24);--rm-shell-border:var(--border-default);--rm-shell-subtle:var(--bg-subtle,#22222e);--rm-shell-text:var(--text-primary);--rm-shell-text-dark:var(--text-secondary);--rm-shell-text-muted:var(--text-muted);--rm-shell-accent:var(--color-primary);--rm-dots-opacity:0.06}.rm-shell{background:var(--rm-shell-bg);color:var(--rm-shell-text);font-family:var(--font-sans,"Figtree",sans-serif);inset:0;overflow:hidden;position:fixed}.rm-shell__dots{background-image:var(--rm-dots-image);background-repeat:repeat;background-size:var(--rm-dots-size) var(--rm-dots-size);inset:0;opacity:var(--rm-dots-opacity);pointer-events:none;position:absolute}.rm-shell__frame{border:1px solid var(--rm-shell-border);border-radius:var(--rm-shell-radius);display:flex;inset:var(--rm-shell-inset);overflow:hidden;position:absolute}.rm-shell__body{display:flex;flex:1;flex-direction:column;min-width:0}.rm-shell__main{flex:1;min-width:0;overflow-y:auto}.rm-sb{background:var(--rm-shell-surface);display:flex;flex-direction:column;flex-shrink:0;height:100%}.rm-sb--left{border-right:1px solid var(--rm-shell-border);position:relative;transition:width .2s;width:var(--rm-nav-w);z-index:200}.rm-sb--left.rm-sb--collapsed{width:var(--rm-nav-collapsed-w)}.rm-sb--right{border-left:1px solid var(--rm-shell-border);width:var(--rm-rail-w)}.rm-sb__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-sb--collapsed .rm-sb__header{justify-content:center;padding:0}.rm-sb__logo{align-items:center;color:var(--rm-shell-text);display:flex;flex:1;font-size:20px;font-weight:700;gap:8px;line-height:1.4;min-width:0;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__toggle{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-sb__toggle:hover{color:var(--rm-shell-text)}.rm-sb__toggle svg{display:block;height:100%;width:100%}.rm-sb__nav{display:flex;flex:1;flex-direction:column;gap:16px;overflow-y:auto;padding:16px}.rm-sb--collapsed .rm-sb__nav{padding-left:8px;padding-right:8px}.rm-sb__group{display:flex;flex-direction:column;gap:4px}.rm-sb__label{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);display:flex;font-family:inherit;font-size:10px;font-weight:500;gap:4px;letter-spacing:.025em;line-height:1.6;margin:0;padding:0 2px;text-align:left;text-transform:uppercase;width:100%}button.rm-sb__label{cursor:pointer}button.rm-sb__label:hover{color:var(--rm-shell-text)}.rm-sb__label-text{flex:1;min-width:0}.rm-sb__chev{display:block;flex-shrink:0;height:12px;transition:transform .2s;width:12px}.rm-sb__label[aria-expanded=true] .rm-sb__chev{transform:rotate(90deg)}.rm-sb__rule{background:var(--rm-shell-border);height:1px;margin:4px}.rm-mi{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;gap:8px;height:26px;margin:0;padding:2px;text-align:left;text-decoration:none;transition:background-color .15s;width:100%}.rm-mi:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-mi.rm-mi--active{background:var(--rm-shell-subtle)}.rm-mi.rm-mi--current{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent)}.rm-mi.rm-mi--current .rm-mi__icon{background:var(--rm-shell-accent);color:#fff}.rm-mi.rm-mi--current .rm-mi__icon svg{stroke:#fff}.rm-mi__icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-mi__icon svg{display:block;height:14px;width:14px}.rm-mi__icon i{font-size:14px;line-height:1}.rm-mi__label{color:var(--rm-shell-text);flex:1;font-size:14px;line-height:1.625;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-mi__badge{background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-muted);flex-shrink:0;font-size:9px;font-weight:700;letter-spacing:.025em;padding:2px 4px;text-transform:uppercase}.rm-sb--collapsed .rm-mi{justify-content:center}.rm-sb--collapsed .rm-mi>:not(.rm-mi__icon),.rm-sb--collapsed .rm-sb__sub{display:none}.rm-sb__sub{display:flex;flex-direction:column;gap:2px;padding:2px 0 2px 28px}.rm-sb__sub-item{border-radius:4px;color:var(--rm-shell-text);display:block;font-size:14px;line-height:1.625;overflow:hidden;padding:2px 4px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__sub-item:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-rail__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;cursor:pointer;display:flex;flex-direction:column;font-family:inherit;gap:4px;margin:0;padding:12px 0 8px;text-decoration:none;width:100%}.rm-rail__avatar{align-items:center;border:2px solid var(--rm-shell-surface);border-radius:9999px;color:#fff;display:flex;font-size:14px;height:32px;justify-content:center;transition:opacity .15s;width:32px}.rm-rail__item:hover .rm-rail__avatar{opacity:.8}.rm-rail__tile{align-items:center;background:var(--rm-shell-subtle);border-radius:8px;color:var(--rm-shell-text-dark);display:flex;font-size:16px;height:32px;justify-content:center;transition:background-color .15s,color .15s;width:32px}.rm-rail__item:hover .rm-rail__tile{background:var(--rm-shell-border)}.rm-rail__item.rm-rail__item--active .rm-rail__tile{background:var(--rm-shell-accent);color:#fff}.rm-rail__tile{position:relative}.rm-rail__badge{background:var(--rm-shell-accent);border-radius:9999px;color:#fff;font-size:10px;font-weight:700;height:16px;line-height:16px;min-width:16px;padding:0 4px;position:absolute;right:-4px;text-align:center;top:-4px}.rm-rail__item--active .rm-rail__badge{background:#fff;color:var(--rm-shell-accent)}.rm-rail__caption{color:var(--rm-shell-text-muted);font-size:10px;line-height:1;max-width:100%;overflow:hidden;padding:0 2px;text-overflow:ellipsis;transition:color .15s;white-space:nowrap}.rm-rail__item--active .rm-rail__caption,.rm-rail__item:hover .rm-rail__caption{color:var(--rm-shell-text)}.rm-rail__bottom{display:flex;justify-content:center;margin-top:auto;padding-bottom:16px}.rm-lang{background:var(--rm-shell-subtle);border:1px solid var(--rm-shell-accent);border-radius:9999px;display:grid;gap:2px;grid-template-rows:1fr 1fr;padding:3px;position:relative}.rm-lang__thumb{background:var(--rm-shell-surface);border-radius:9999px;box-shadow:0 1px 4px rgba(30,41,59,.16);height:32px;left:3px;position:absolute;top:3px;transition:transform .26s cubic-bezier(.34,1.56,.64,1);width:32px}.rm-lang--second .rm-lang__thumb{transform:translateY(34px)}.rm-lang__option{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:9999px;box-shadow:none;cursor:pointer;display:flex;filter:grayscale(1);font-size:17px;height:32px;justify-content:center;line-height:1;margin:0;opacity:.5;padding:0;position:relative;transition:filter .18s ease,opacity .18s ease,transform .18s ease;width:32px}.rm-lang__option:hover{filter:grayscale(0);opacity:.85}.rm-lang__option.rm-lang__option--active{filter:grayscale(0);opacity:1}.rm-lang__option:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}@media (prefers-reduced-motion:reduce){.rm-lang__option,.rm-lang__thumb{transition:none}}.rm-hd{background:var(--rm-shell-surface);border-bottom:1px solid var(--rm-shell-border);display:flex;flex-direction:column;flex-shrink:0}.rm-hd__row{gap:16px;height:var(--rm-bar-h);padding:0 16px}.rm-hd__crumbs,.rm-hd__row{align-items:center;display:flex}.rm-hd__crumbs{flex:1;font-size:14px;gap:4px;min-width:0}.rm-hd__crumb{align-items:center;color:var(--rm-shell-text-muted);display:inline-flex;gap:6px;text-decoration:none;transition:color .15s;white-space:nowrap}a.rm-hd__crumb:hover{color:var(--rm-shell-text)}.rm-hd__sep{color:var(--rm-shell-text-muted)}.rm-hd__picker{display:inline-flex;min-width:0;position:relative}.rm-hd__crumb--picker{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;cursor:pointer;font:inherit;font-size:14px;margin:0;padding:2px 4px;transition:background-color .15s,color .15s}.rm-hd__crumb--picker:hover{background:var(--rm-shell-subtle)}.rm-hd__caret{display:block;flex-shrink:0;height:12px;opacity:.7;transition:transform .15s;width:12px}.rm-hd__caret--open{transform:rotate(180deg)}.rm-hd__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;box-shadow:0 8px 24px rgba(30,41,59,.12);left:0;max-height:60vh;max-width:320px;min-width:200px;overflow-y:auto;padding:4px;position:absolute;top:calc(100% + 4px);z-index:300}.rm-hd__menu-item{align-items:center;border-radius:4px;color:var(--rm-shell-text);display:flex;font-size:14px;gap:8px;line-height:1.5;overflow:hidden;padding:4px 6px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-hd__menu-item:hover{background:var(--rm-shell-subtle)}.rm-hd__menu-group{color:var(--rm-shell-text-muted);display:block;font-size:10px;font-weight:500;letter-spacing:.025em;line-height:1.6;padding:8px 6px 2px;text-transform:uppercase}.rm-hd__menu-group:first-child{padding-top:2px}.rm-hd__menu-item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-hd__menu-icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-hd__menu-icon i{font-size:14px;line-height:1}.rm-hd__menu-item--active .rm-hd__menu-icon{background:var(--rm-shell-accent);color:#fff}.rm-hd__crumb--last{color:var(--rm-shell-text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-hd__search{align-items:center;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;display:flex;flex-shrink:0;gap:4px;padding:4px 12px;width:300px}.rm-hd__search input{background:transparent;border:none;color:var(--rm-shell-text);flex:1;font-family:inherit;font-size:14px;line-height:20px;min-width:0;outline:none;padding:0}.rm-hd__search input::-moz-placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search input::placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search i,.rm-hd__search svg{color:var(--rm-shell-text-muted);display:block;flex-shrink:0;font-size:14px;height:14px;line-height:20px;width:14px}.rm-hd__tabs{display:flex;gap:16px;padding:0 16px}.rm-hd__tab{align-items:flex-start;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:var(--rm-shell-text-dark);cursor:pointer;display:flex;flex-direction:column;font-family:inherit;font-size:14px;font-weight:700;line-height:1.6;margin:0;padding:4px 4px 8px;white-space:nowrap}.rm-hd__tab--active{border-bottom-color:var(--rm-shell-accent);color:var(--rm-shell-text)}@media (max-width:767px){.rm-sb--left{inset:0 auto 0 0;position:absolute;transform:translateX(-100%);transition:transform .2s;z-index:40}.rm-sb--left.rm-sb--open{transform:translateX(0)}.rm-sb--right{display:none}.rm-hd__search{flex:1;width:auto}}.rm-panel{background:var(--rm-shell-surface);border-left:1px solid var(--rm-shell-border);bottom:0;box-shadow:-8px 0 24px rgba(30,41,59,.08);display:flex;flex-direction:column;position:absolute;right:0;top:0;width:var(--rm-panel-w,360px);z-index:401}.rm-panel__veil{backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);background:var(--rm-panel-veil,rgba(100,116,139,.12));cursor:default;inset:0;position:absolute;z-index:400}.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:opacity .22s ease}.rm-panel-veil-enter-from,.rm-panel-veil-leave-to{opacity:0}@media (prefers-reduced-motion:reduce){.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:none}}.rm-sb--right{position:relative;z-index:402}.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:transform .22s cubic-bezier(.4,0,.2,1)}.rm-panel-enter-from,.rm-panel-leave-to{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:none}}.rm-panel__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-panel__title{color:var(--rm-shell-text);flex:1;font-size:14px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-panel__close{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-panel__close:hover{color:var(--rm-shell-text)}.rm-panel__close svg{display:block;height:100%;width:100%}.rm-panel__body{flex:1;min-height:0;overflow-y:auto;padding:16px}@media (max-width:767px){.rm-panel{border-left:none;left:0;width:auto}}
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  export interface RmButtonProps {
3
3
  /** Visual style variant */
4
- variant?: 'primary-fill' | 'primary-stroke' | 'discovery-fill' | 'discovery-stroke' | 'databank-fill' | 'databank-stroke' | 'pharma-fill' | 'pharma-stroke' | 'secondary' | 'outline' | 'ghost' | 'danger'
4
+ variant?: 'primary' | 'primary-fill' | 'primary-stroke' | 'discovery-fill' | 'discovery-stroke' | 'databank-fill' | 'databank-stroke' | 'pharma-fill' | 'pharma-stroke' | 'secondary' | 'outline' | 'ghost' | 'danger'
5
5
  /** Size of the button */
6
6
  size?: 'sm' | 'md' | 'lg'
7
7
  /** Whether the button is disabled */
@@ -50,5 +50,5 @@ defineEmits<{
50
50
  </template>
51
51
 
52
52
  <style scoped>
53
- .rm-button{align-items:center;border:1px solid transparent;border-radius:6px;cursor:pointer;display:inline-flex;font-family:var(--font-sans);font-weight:500;gap:8px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.rm-button:focus-visible{outline:2px solid var(--color-healthcare);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(var(--healthcare-rgb),.8) 0,rgba(var(--healthcare-rgb),.9) 100%);border-color:var(--color-healthcare);box-shadow:0 2px 3px 0 rgba(var(--healthcare-rgb),.5);color:var(--color-text-inverse)}.rm-button--primary-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-healthcare) 0,var(--color-healthcare) 100%);box-shadow:0 2px 4px 0 rgba(var(--healthcare-rgb),.6)}.rm-button--primary-fill:active:not(:disabled){background:var(--color-healthcare-active);box-shadow:0 1px 2px 0 rgba(var(--healthcare-rgb),.4)}.rm-button--primary-stroke{box-shadow:0 2px 3px 0 rgba(var(--healthcare-rgb),.5);color:var(--color-healthcare)}.rm-button--primary-stroke,.rm-button--primary-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-healthcare)}.rm-button--primary-stroke:hover:not(:disabled){box-shadow:none}.rm-button--primary-stroke:active:not(:disabled){background:var(--bg-hover);box-shadow:none}.rm-button--discovery-fill{background:linear-gradient(180deg,rgba(var(--discovery-rgb),.8) 0,rgba(var(--discovery-rgb),.9) 100%);border-color:var(--color-discovery);box-shadow:0 2px 3px 0 rgba(var(--discovery-rgb),.5);color:var(--color-text-inverse)}.rm-button--discovery-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-discovery) 0,var(--color-discovery) 100%);border-color:var(--color-discovery);box-shadow:none}.rm-button--discovery-fill:active:not(:disabled){background:linear-gradient(180deg,var(--color-discovery-active) 0,var(--color-discovery-active) 100%);box-shadow:none}.rm-button--discovery-stroke{box-shadow:0 2px 3px 0 rgba(var(--discovery-rgb),.5);color:var(--color-discovery)}.rm-button--discovery-stroke,.rm-button--discovery-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-discovery)}.rm-button--discovery-stroke:hover:not(:disabled){box-shadow:none}.rm-button--discovery-stroke:active:not(:disabled){background:rgba(var(--discovery-rgb),.05);box-shadow:none}.rm-button--databank-fill{background:linear-gradient(180deg,rgba(var(--databank-rgb),.8) 0,rgba(var(--databank-rgb),.9) 100%);border-color:var(--color-databank);box-shadow:0 2px 3px 0 rgba(var(--databank-rgb),.5);color:var(--color-text-inverse)}.rm-button--databank-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-databank) 0,var(--color-databank) 100%);border-color:var(--color-databank);box-shadow:none}.rm-button--databank-fill:active:not(:disabled){background:linear-gradient(180deg,var(--color-databank-active) 0,var(--color-databank-active) 100%);box-shadow:none}.rm-button--databank-stroke{box-shadow:0 2px 3px 0 rgba(var(--databank-rgb),.5);color:var(--color-databank)}.rm-button--databank-stroke,.rm-button--databank-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-databank)}.rm-button--databank-stroke:hover:not(:disabled){box-shadow:none}.rm-button--databank-stroke:active:not(:disabled){background:rgba(var(--databank-rgb),.05);box-shadow:none}.rm-button--pharma-fill{background:linear-gradient(180deg,var(--color-pharma-soft) 0,var(--color-pharma) 100%);border-color:var(--color-pharma);box-shadow:0 2px 3px 0 rgba(var(--pharma-rgb),.5);color:var(--color-text-inverse)}.rm-button--pharma-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-pharma) 0,var(--color-pharma) 100%);border-color:var(--color-pharma);box-shadow:none}.rm-button--pharma-fill:active:not(:disabled){background:linear-gradient(180deg,var(--color-pharma-active) 0,var(--color-pharma-active) 100%);box-shadow:none}.rm-button--pharma-stroke{box-shadow:0 2px 3px 0 rgba(var(--pharma-rgb),.5);color:var(--color-pharma)}.rm-button--pharma-stroke,.rm-button--pharma-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-pharma)}.rm-button--pharma-stroke:hover:not(:disabled){box-shadow:none}.rm-button--pharma-stroke:active:not(:disabled){background:rgba(var(--pharma-rgb),.05);box-shadow:none}.rm-button--secondary{background-color:var(--bg-surface-3);color:var(--text-primary)}.rm-button--secondary:active:not(:disabled),.rm-button--secondary:hover:not(:disabled){background-color:var(--border-default)}.rm-button--outline{background-color:transparent;border-color:var(--border-default);color:var(--text-secondary)}.rm-button--outline:hover:not(:disabled){background-color:var(--bg-surface-2);border-color:var(--text-muted)}.rm-button--outline:active:not(:disabled){background-color:var(--bg-surface-3)}.rm-button--danger{background-color:var(--bg-surface);border-color:var(--color-danger);color:var(--color-danger)}.rm-button--danger:active:not(:disabled),.rm-button--danger:hover:not(:disabled){background-color:var(--color-danger);color:var(--color-text-inverse)}.rm-button--ghost{background-color:transparent;color:var(--text-secondary)}.rm-button--ghost:hover:not(:disabled){background-color:var(--bg-surface-3)}.rm-button--ghost:active:not(:disabled){background-color:var(--border-default)}.rm-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(--font-sans);font-weight:500;gap:8px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.rm-button:focus-visible{outline:2px solid var(--color-healthcare);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{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.rm-button--primary:active:not(:disabled),.rm-button--primary:hover:not(:disabled){background:var(--color-primary-hover);border-color:var(--color-primary-hover)}.rm-button--primary-fill{background:linear-gradient(180deg,rgba(var(--healthcare-rgb),.8) 0,rgba(var(--healthcare-rgb),.9) 100%);border-color:var(--color-healthcare);box-shadow:0 2px 3px 0 rgba(var(--healthcare-rgb),.5);color:var(--color-text-inverse)}.rm-button--primary-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-healthcare) 0,var(--color-healthcare) 100%);box-shadow:0 2px 4px 0 rgba(var(--healthcare-rgb),.6)}.rm-button--primary-fill:active:not(:disabled){background:var(--color-healthcare-active);box-shadow:0 1px 2px 0 rgba(var(--healthcare-rgb),.4)}.rm-button--primary-stroke{box-shadow:0 2px 3px 0 rgba(var(--healthcare-rgb),.5);color:var(--color-healthcare)}.rm-button--primary-stroke,.rm-button--primary-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-healthcare)}.rm-button--primary-stroke:hover:not(:disabled){box-shadow:none}.rm-button--primary-stroke:active:not(:disabled){background:var(--bg-hover);box-shadow:none}.rm-button--discovery-fill{background:linear-gradient(180deg,rgba(var(--discovery-rgb),.8) 0,rgba(var(--discovery-rgb),.9) 100%);border-color:var(--color-discovery);box-shadow:0 2px 3px 0 rgba(var(--discovery-rgb),.5);color:var(--color-text-inverse)}.rm-button--discovery-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-discovery) 0,var(--color-discovery) 100%);border-color:var(--color-discovery);box-shadow:none}.rm-button--discovery-fill:active:not(:disabled){background:linear-gradient(180deg,var(--color-discovery-active) 0,var(--color-discovery-active) 100%);box-shadow:none}.rm-button--discovery-stroke{box-shadow:0 2px 3px 0 rgba(var(--discovery-rgb),.5);color:var(--color-discovery)}.rm-button--discovery-stroke,.rm-button--discovery-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-discovery)}.rm-button--discovery-stroke:hover:not(:disabled){box-shadow:none}.rm-button--discovery-stroke:active:not(:disabled){background:rgba(var(--discovery-rgb),.05);box-shadow:none}.rm-button--databank-fill{background:linear-gradient(180deg,rgba(var(--databank-rgb),.8) 0,rgba(var(--databank-rgb),.9) 100%);border-color:var(--color-databank);box-shadow:0 2px 3px 0 rgba(var(--databank-rgb),.5);color:var(--color-text-inverse)}.rm-button--databank-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-databank) 0,var(--color-databank) 100%);border-color:var(--color-databank);box-shadow:none}.rm-button--databank-fill:active:not(:disabled){background:linear-gradient(180deg,var(--color-databank-active) 0,var(--color-databank-active) 100%);box-shadow:none}.rm-button--databank-stroke{box-shadow:0 2px 3px 0 rgba(var(--databank-rgb),.5);color:var(--color-databank)}.rm-button--databank-stroke,.rm-button--databank-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-databank)}.rm-button--databank-stroke:hover:not(:disabled){box-shadow:none}.rm-button--databank-stroke:active:not(:disabled){background:rgba(var(--databank-rgb),.05);box-shadow:none}.rm-button--pharma-fill{background:linear-gradient(180deg,var(--color-pharma-soft) 0,var(--color-pharma) 100%);border-color:var(--color-pharma);box-shadow:0 2px 3px 0 rgba(var(--pharma-rgb),.5);color:var(--color-text-inverse)}.rm-button--pharma-fill:hover:not(:disabled){background:linear-gradient(180deg,var(--color-pharma) 0,var(--color-pharma) 100%);border-color:var(--color-pharma);box-shadow:none}.rm-button--pharma-fill:active:not(:disabled){background:linear-gradient(180deg,var(--color-pharma-active) 0,var(--color-pharma-active) 100%);box-shadow:none}.rm-button--pharma-stroke{box-shadow:0 2px 3px 0 rgba(var(--pharma-rgb),.5);color:var(--color-pharma)}.rm-button--pharma-stroke,.rm-button--pharma-stroke:hover:not(:disabled){background:linear-gradient(180deg,rgba(var(--surface-rgb),.5) 0,rgba(var(--surface-rgb),.9) 100%);border-color:var(--color-pharma)}.rm-button--pharma-stroke:hover:not(:disabled){box-shadow:none}.rm-button--pharma-stroke:active:not(:disabled){background:rgba(var(--pharma-rgb),.05);box-shadow:none}.rm-button--secondary{background-color:var(--bg-surface);border-color:var(--border-default);color:var(--text-secondary)}.rm-button--secondary:active:not(:disabled),.rm-button--secondary:hover:not(:disabled){background-color:var(--bg-surface-3);border-color:var(--color-violet-pale);color:var(--text-primary)}.rm-button--outline{background-color:transparent;border-color:var(--border-default);color:var(--text-secondary)}.rm-button--outline:hover:not(:disabled){background-color:var(--bg-surface-2);border-color:var(--text-muted)}.rm-button--outline:active:not(:disabled){background-color:var(--bg-surface-3)}.rm-button--danger{background-color:var(--bg-surface);border-color:var(--color-danger);color:var(--color-danger)}.rm-button--danger:active:not(:disabled),.rm-button--danger:hover:not(:disabled){background-color:var(--color-danger);color:var(--color-text-inverse)}.rm-button--ghost{background-color:transparent;color:var(--text-secondary)}.rm-button--ghost:hover:not(:disabled){background-color:var(--bg-surface-3)}.rm-button--ghost:active:not(:disabled){background-color:var(--border-default)}.rm-button--block{width:100%}
54
54
  </style>
@@ -20,8 +20,14 @@ const props = withDefaults(defineProps<{
20
20
  context?: string
21
21
  /** Organisme public : "HAS", "OCDE", "ANSM (Repertoire des specialites)". Jamais un outil interne. */
22
22
  source: string
23
- /** Date de notre verification. Un Date est formate en francais long ; une chaine deja formatee passe telle quelle. */
23
+ /** Date de notre verification. Un Date est formate en long ; une chaine deja formatee passe telle quelle. */
24
24
  verified: Date | string | number
25
+ /**
26
+ * Langue du chrome de la carte : pied, menu d'actions, corps vide ou en
27
+ * erreur. Le titre, le sous-titre et la source restent a la page, qui les
28
+ * ecrit deja dans sa langue.
29
+ */
30
+ lang?: 'fr' | 'en'
25
31
  /** Complement de fin de foot : frequence, millesime. */
26
32
  extra?: string
27
33
  /** Masque le menu d'actions quand la carte n'a rien a exporter. */
@@ -41,6 +47,7 @@ const props = withDefaults(defineProps<{
41
47
  emptyActionLabel?: string
42
48
  }>(), {
43
49
  context: undefined,
50
+ lang: 'fr',
44
51
  extra: undefined,
45
52
  actions: true,
46
53
  state: 'ready',
@@ -64,11 +71,54 @@ const emit = defineEmits<{
64
71
  const menuOpen = ref(false)
65
72
  const root = ref<HTMLElement | null>(null)
66
73
 
74
+ /** Le chrome de la carte, dans les deux langues du portail. */
75
+ const WORDS = {
76
+ fr: {
77
+ actions: 'Actions sur cette carte',
78
+ exportSection: 'Exporter',
79
+ png: 'Image PNG',
80
+ svg: 'Vectoriel SVG',
81
+ csv: 'Données CSV',
82
+ xlsx: 'Excel (XLSX)',
83
+ shareSection: 'Partager',
84
+ copyLink: 'Copier le lien direct',
85
+ fullscreen: 'Plein écran',
86
+ source: 'Source :',
87
+ verified: 'Vérifié le',
88
+ emptyTitle: 'Aucun résultat',
89
+ emptyText:
90
+ 'Aucune ligne ne correspond aux filtres en cours. Élargissez votre période ou retirez une dimension.',
91
+ errorText: "La donnée de cette carte n'a pas pu être chargée.",
92
+ },
93
+ en: {
94
+ actions: 'Card actions',
95
+ exportSection: 'Export',
96
+ png: 'PNG image',
97
+ svg: 'SVG vector',
98
+ csv: 'CSV data',
99
+ xlsx: 'Excel (XLSX)',
100
+ shareSection: 'Share',
101
+ copyLink: 'Copy direct link',
102
+ fullscreen: 'Fullscreen',
103
+ source: 'Source:',
104
+ verified: 'Verified on',
105
+ emptyTitle: 'No results',
106
+ emptyText:
107
+ 'No row matches the current filters. Widen your period or drop a dimension.',
108
+ errorText: 'This card could not load its data.',
109
+ },
110
+ } as const
111
+
112
+ const t = computed(() => WORDS[props.lang])
113
+
67
114
  // Une chaine deja formatee ("17 juin 2026") n'est pas parsable : on la garde
68
115
  // telle quelle plutot que d'afficher "Invalid Date".
69
116
  const verifiedText = computed(() => {
70
117
  const parsed = new Date(props.verified)
71
- return Number.isNaN(parsed.getTime()) ? String(props.verified) : rmDate(parsed)
118
+ if (Number.isNaN(parsed.getTime())) return String(props.verified)
119
+ return props.lang === 'en'
120
+ ? parsed.toLocaleDateString('en-US', { day: 'numeric', month: 'long', year: 'numeric' })
121
+ : rmDate(parsed)
72
122
  })
73
123
 
74
124
  function onDocumentClick(event: MouseEvent) {
@@ -104,33 +154,33 @@ function run(action: 'exportPng' | 'exportSvg' | 'exportCsv' | 'exportXlsx' | 'c
104
154
  <button
105
155
  class="cc-export"
106
156
  type="button"
107
- aria-label="Actions sur cette carte"
157
+ :aria-label="t.actions"
108
158
  :aria-expanded="menuOpen"
109
159
  @click.stop="menuOpen = !menuOpen"
110
160
  >
111
161
  <RmIcon name="Gear" :size="14" />
112
162
  </button>
113
163
  <div class="cc-menu" role="menu">
114
- <div class="cc-menu-section">Exporter</div>
164
+ <div class="cc-menu-section">{{ t.exportSection }}</div>
115
165
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportPng')">
116
- <RmIcon name="Image" :size="14" /> Image PNG
166
+ <RmIcon name="Image" :size="14" /> {{ t.png }}
117
167
  </button>
118
168
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportSvg')">
119
- <RmIcon name="VectorThree" :size="14" /> Vectoriel SVG
169
+ <RmIcon name="VectorThree" :size="14" /> {{ t.svg }}
120
170
  </button>
121
171
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportCsv')">
122
- <RmIcon name="FileCsv" :size="14" /> Données CSV
172
+ <RmIcon name="FileCsv" :size="14" /> {{ t.csv }}
123
173
  </button>
124
174
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportXlsx')">
125
- <RmIcon name="FileXls" :size="14" /> Excel (XLSX)
175
+ <RmIcon name="FileXls" :size="14" /> {{ t.xlsx }}
126
176
  </button>
127
177
  <div class="cc-menu-divider" />
128
- <div class="cc-menu-section">Partager</div>
178
+ <div class="cc-menu-section">{{ t.shareSection }}</div>
129
179
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('copyLink')">
130
- <RmIcon name="Link" :size="14" /> Copier le lien direct
180
+ <RmIcon name="Link" :size="14" /> {{ t.copyLink }}
131
181
  </button>
132
182
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('fullscreen')">
133
- <RmIcon name="ArrowsOut" :size="14" /> Plein écran
183
+ <RmIcon name="ArrowsOut" :size="14" /> {{ t.fullscreen }}
134
184
  </button>
135
185
  </div>
136
186
  </div>
@@ -148,8 +198,8 @@ function run(action: 'exportPng' | 'exportSvg' | 'exportCsv' | 'exportXlsx' | 'c
148
198
  <slot v-else-if="state === 'empty'" name="empty">
149
199
  <RmEmptyState
150
200
  icon="MagnifyingGlassMinus"
151
- title="Aucun résultat"
152
- text="Aucune ligne ne correspond aux filtres en cours. Élargissez votre période ou retirez une dimension."
201
+ :title="t.emptyTitle"
202
+ :text="t.emptyText"
153
203
  :action-label="emptyActionLabel"
154
204
  action-icon="Broom"
155
205
  @action="emit('emptyAction')"
@@ -157,7 +207,7 @@ function run(action: 'exportPng' | 'exportSvg' | 'exportCsv' | 'exportXlsx' | 'c
157
207
  </slot>
158
208
  <slot v-else name="error">
159
209
  <RmErrorState
160
- text="La donnée de cette carte n'a pas pu être chargée."
210
+ :text="t.errorText"
161
211
  :action-label="retryLabel"
162
212
  @action="emit('retry')"
163
213
  />
@@ -165,9 +215,9 @@ function run(action: 'exportPng' | 'exportSvg' | 'exportCsv' | 'exportXlsx' | 'c
165
215
  </div>
166
216
 
167
217
  <div class="chart-card-foot">
168
- <span class="src">Source : <b>{{ source }}</b></span>
218
+ <span class="src">{{ t.source }} <b>{{ source }}</b></span>
169
219
  <span class="dot" />
170
- <span class="updated">Vérifié le {{ verifiedText }}</span>
220
+ <span class="updated">{{ t.verified }} {{ verifiedText }}</span>
171
221
  <template v-if="extra">
172
222
  <span class="dot" />
173
223
  <span>{{ extra }}</span>
@@ -250,7 +250,10 @@ function enabled(section: RmExportSection) {
250
250
  if (!format.value) return false
251
251
  if (section.kind === 'table') return !!format.value.tabular
252
252
  if (section.kind === 'visual') return !!format.value.visual
253
- return !!format.value.paged
253
+ // Une fiche suppose une mise en page ; un en-tete de document suppose une
254
+ // page de garde. Le classeur a la seconde sans la premiere.
255
+ if (section.kind === 'cards') return !!format.value.paged
256
+ return !!format.value.document
254
257
  }
255
258
 
256
259
  const showColumns = computed(() => !!format.value?.tabular && props.columns.length > 0)
@@ -5,10 +5,16 @@
5
5
  se colle en haut de la colonne de contenu quand elle defile ; la barre de
6
6
  filtres (RmFilterBar) se cale sous lui en suivant son id `rm_page_header`.
7
7
 
8
- Slots : #title (a la place de la prop), #actions (a droite du titre).
8
+ Il porte aussi les onglets de la page, sous le titre : c'etait la derniere
9
+ raison de garder `arg-header` dans les apps. Le dessin est celui des onglets
10
+ du bandeau de cadre (RmAppHeader) : libelle souligne quand il est actif.
11
+
12
+ Slots : #title (a la place de la prop), #actions (a droite du titre),
13
+ defaut (sous la rangee du titre, avant les onglets).
9
14
  -->
10
15
  <script setup lang="ts">
11
16
  import { useRouter } from 'vue-router'
17
+ import type { RmHeaderTab } from './AppHeader.vue'
12
18
 
13
19
  withDefaults(
14
20
  defineProps<{
@@ -17,10 +23,16 @@ withDefaults(
17
23
  haveReturn?: boolean
18
24
  /** Destination du retour ; sans valeur, l'historique. */
19
25
  backTo?: string | null
26
+ /** Onglets de la page. La page dit lequel est actif et ecoute `tab`. */
27
+ tabs?: RmHeaderTab[]
28
+ /** Nom du groupe d'onglets pour les lecteurs d'ecran. */
29
+ tabsLabel?: string
20
30
  }>(),
21
- { title: null, haveReturn: false, backTo: null },
31
+ { title: null, haveReturn: false, backTo: null, tabs: () => [], tabsLabel: undefined },
22
32
  )
23
33
 
34
+ const emit = defineEmits<{ tab: [key: string] }>()
35
+
24
36
  const router = useRouter()
25
37
  </script>
26
38
 
@@ -42,9 +54,22 @@ const router = useRouter()
42
54
  <div v-if="$slots.actions" class="rm-page-header__actions"><slot name="actions" /></div>
43
55
  </div>
44
56
  <slot />
57
+
58
+ <div v-if="tabs.length" class="rm-page-header__tabs" role="tablist" :aria-label="tabsLabel">
59
+ <button
60
+ v-for="tab in tabs"
61
+ :key="tab.key"
62
+ class="rm-page-header__tab"
63
+ :class="{ 'rm-page-header__tab--active': tab.active }"
64
+ type="button"
65
+ role="tab"
66
+ :aria-selected="tab.active ? 'true' : 'false'"
67
+ @click="emit('tab', tab.key)"
68
+ >{{ tab.label }}</button>
69
+ </div>
45
70
  </header>
46
71
  </template>
47
72
 
48
73
  <style scoped>
49
- .rm-page-header{backdrop-filter:blur(12px);background:rgba(245,245,249,.8);border-bottom:1px solid var(--border-default);box-sizing:border-box;color:var(--text-primary);display:flex;flex-direction:column;font-family:var(--font-sans);gap:12px;padding:16px 32px 12px;position:sticky;top:0;width:100%;z-index:95}.rm-page-header__row{align-items:center;align-self:stretch;display:flex;gap:16px}.rm-page-header__back{background:transparent;border:none;color:inherit;cursor:pointer;display:inline-flex;padding:0}.rm-page-header__title{flex-grow:2;font-size:23px;font-weight:900;line-height:normal;margin:0;min-width:0}.rm-page-header__title :deep(h1),.rm-page-header__title :deep(h2),.rm-page-header__title :deep(p){font-size:inherit;font-weight:inherit;line-height:inherit;margin:0}.rm-page-header__actions{align-items:center;display:flex;flex:0 0 auto;gap:16px}
74
+ .rm-page-header{backdrop-filter:blur(12px);background:rgba(245,245,249,.8);border-bottom:1px solid var(--border-default);box-sizing:border-box;color:var(--text-primary);display:flex;flex-direction:column;font-family:var(--font-sans);gap:12px;padding:16px 32px 12px;position:sticky;top:0;width:100%;z-index:95}.rm-page-header__row{align-items:center;align-self:stretch;display:flex;gap:16px}.rm-page-header__back{background:transparent;border:none;color:inherit;cursor:pointer;display:inline-flex;padding:0}.rm-page-header__title{flex-grow:2;font-size:23px;font-weight:900;line-height:normal;margin:0;min-width:0}.rm-page-header__title :deep(h1),.rm-page-header__title :deep(h2),.rm-page-header__title :deep(p){font-size:inherit;font-weight:inherit;line-height:inherit;margin:0}.rm-page-header__actions{align-items:center;display:flex;flex:0 0 auto;gap:16px}.rm-page-header__tabs{display:flex;gap:8px;margin-bottom:-13px;overflow-x:auto}.rm-page-header__tab{background:none;border:none;border-bottom:3px solid transparent;border-radius:8px 8px 0 0;color:var(--text-muted);cursor:pointer;font-family:inherit;font-size:14px;font-weight:500;line-height:1.6;padding:8px 16px 10px;transition:background-color .3s,color .15s;white-space:nowrap}.rm-page-header__tab:hover{color:var(--color-primary)}.rm-page-header__tab--active{background:color-mix(in srgb,var(--color-primary) 6%,transparent);border-bottom-color:var(--color-primary);color:var(--color-primary);font-weight:600}
50
75
  </style>
@@ -11,6 +11,10 @@
11
11
 
12
12
  L'en-tete reprend la rangee de 56px du bandeau pour que le titre du panneau
13
13
  s'aligne sur le fil d'Ariane, a la ligne pres.
14
+
15
+ Un voile flou et grise couvre le reste du cadre pendant l'ouverture, barre
16
+ laterale comprise : le panneau est ce qu'on lit, pas ce qu'on lisait. Un
17
+ clic sur le voile referme le panneau, comme un tiroir.
14
18
  -->
15
19
  <script setup lang="ts">
16
20
  withDefaults(defineProps<{
@@ -30,6 +34,16 @@ const emit = defineEmits<{ close: [] }>()
30
34
  </script>
31
35
 
32
36
  <template>
37
+ <!-- Voile : le panneau ouvert prend la main, le reste du cadre passe en
38
+ flou grise. Un element a part plutot qu'un `filter` sur le contenu,
39
+ qui creerait un bloc conteneur et decrocherait les bandeaux collants
40
+ des pages. Il prend les clics et referme le panneau : c'est le geste
41
+ attendu de tout ce qui voile la page. Le clavier, lui, garde le bouton
42
+ de fermeture de l'en-tete, seul element expose. -->
43
+ <Transition name="rm-panel-veil">
44
+ <div v-if="open" class="rm-panel__veil" aria-hidden="true" @click="emit('close')" />
45
+ </Transition>
46
+
33
47
  <Transition name="rm-panel">
34
48
  <aside v-if="open" class="rm-panel" :style="{ '--rm-panel-w': width }">
35
49
  <div class="rm-panel__header">
@@ -204,7 +204,7 @@ function onCardClick() {
204
204
  </span>
205
205
  <RmButton
206
206
  v-else
207
- variant="primary-fill"
207
+ variant="primary"
208
208
  size="sm"
209
209
  @click="emit('activate', view.id)"
210
210
  >
@@ -49,6 +49,8 @@ const props = withDefaults(
49
49
  loading?: boolean
50
50
  /** Nom de la vue active dont la configuration a change a l'ecran. */
51
51
  dirtyTitle?: string
52
+ /** Retire la recherche et les pastilles de filtre. */
53
+ hideFilters?: boolean
52
54
  /** Textes de l'etat vide. */
53
55
  emptyTitle?: string
54
56
  emptyText?: string
@@ -67,6 +69,7 @@ const props = withDefaults(
67
69
  starrable: false,
68
70
  loading: false,
69
71
  dirtyTitle: '',
72
+ hideFilters: false,
70
73
  emptyTitle: '',
71
74
  emptyText: '',
72
75
  emptyActionLabel: '',
@@ -163,7 +166,7 @@ const visibleTabs = computed(() => (props.tabs.length > 1 ? props.tabs : []))
163
166
  <h2 v-if="title" class="rm-views__title">{{ title }}</h2>
164
167
  <RmButton
165
168
  v-if="saveLabel"
166
- variant="primary-fill"
169
+ variant="primary"
167
170
  size="sm"
168
171
  prefix-icon="BookmarkSimple"
169
172
  :disabled="saveDisabled"
@@ -193,7 +196,10 @@ const visibleTabs = computed(() => (props.tabs.length > 1 ? props.tabs : []))
193
196
  </button>
194
197
  </div>
195
198
 
196
- <template v-if="views.length > 1">
199
+ <!-- La recherche et les pastilles restent posees meme sans vue : la
200
+ reference les montre toujours, et les faire apparaitre a la premiere
201
+ vue enregistree ferait sauter la mise en page du panneau. -->
202
+ <template v-if="!hideFilters">
197
203
  <label class="rm-views__search">
198
204
  <RmIcon name="MagnifyingGlass" :size="14" />
199
205
  <input v-model="search" type="text" :placeholder="T.search">
@@ -225,7 +231,7 @@ const visibleTabs = computed(() => (props.tabs.length > 1 ? props.tabs : []))
225
231
  <RmButton variant="secondary" size="sm" @click="emit('save-as')">
226
232
  {{ T.saveAs }}
227
233
  </RmButton>
228
- <RmButton variant="primary-fill" size="sm" @click="emit('save')">
234
+ <RmButton variant="primary" size="sm" @click="emit('save')">
229
235
  {{ T.save }}
230
236
  </RmButton>
231
237
  </template>
@@ -77,15 +77,46 @@ const collapsibles = computed(() =>
77
77
  props.sections.filter(s => s.label && s.collapsible !== false),
78
78
  )
79
79
 
80
- /** Section ouverte : celle qui contient le chemin courant, sinon la premiere. */
81
- const sectionOf = (path?: string) =>
82
- collapsibles.value.find(s => s.items.some(i => i.to === path))?.key
83
- ?? collapsibles.value[0]?.key
84
- ?? null
80
+ /** Chemin comparable : sans query ni slash final. */
81
+ const trim = (path: string) => (path.split('?')[0] || '').replace(/\/+$/, '') || '/'
82
+
83
+ /**
84
+ * Section ouverte : celle qui porte la page courante, sinon la premiere.
85
+ *
86
+ * L'entree marquee `active` decide d'abord. C'est l'app qui la calcule, et
87
+ * elle en sait plus que nous : son `to` passe par `localePath`, donc prefixe
88
+ * de la locale et parfois suivi d'un slash, quand `activePath` est brut. Une
89
+ * egalite stricte entre les deux ne tombait jamais juste et toutes les pages
90
+ * repliaient la premiere section.
91
+ *
92
+ * A defaut de drapeau, le chemin sert de recours : egalite d'abord, puis
93
+ * prefixe, pour qu'une sous-page (« /prices/jo ») ouvre la section de son
94
+ * entree.
95
+ */
96
+ const sectionOf = (path?: string) => {
97
+ const byActive = collapsibles.value.find(s => s.items.some(i => i.active))
98
+ if (byActive) return byActive.key
99
+
100
+ const here = path ? trim(path) : null
101
+ if (here) {
102
+ const byPath = collapsibles.value.find(s =>
103
+ s.items.some((i) => {
104
+ if (!i.to) return false
105
+ const to = trim(i.to)
106
+ return here === to || here.startsWith(`${to}/`)
107
+ }),
108
+ )
109
+ if (byPath) return byPath.key
110
+ }
111
+
112
+ return collapsibles.value[0]?.key ?? null
113
+ }
85
114
 
86
115
  const openSection = ref<string | null>(sectionOf(props.activePath))
87
- watch(() => props.activePath, (path) => {
88
- const key = sectionOf(path)
116
+ // La cible se deduit des sections autant que du chemin : les deux arrivent
117
+ // ensemble a la navigation, et les droits d'acces peuvent recomposer la liste
118
+ // en cours de route.
119
+ watch(() => sectionOf(props.activePath), (key) => {
89
120
  if (key && key !== openSection.value) openSection.value = key
90
121
  })
91
122
 
@@ -20,6 +20,12 @@ export interface RmRailTile {
20
20
  label: string
21
21
  /** Icone Phosphor, nom en kebab-case sans le prefixe `ph-`. */
22
22
  icon: string
23
+ /**
24
+ * Pastille de comptage posee sur la tuile (nombre de sources de la page,
25
+ * par exemple). Rien n'est dessine a zero ou sans valeur : une pastille
26
+ * vide dirait qu'il y a quelque chose a voir.
27
+ */
28
+ count?: number
23
29
  }
24
30
 
25
31
  export interface RmRailLocale {
@@ -87,11 +93,14 @@ const second = computed(() =>
87
93
  class="rm-rail__item"
88
94
  :class="{ 'rm-rail__item--active': active === tile.key }"
89
95
  type="button"
90
- :title="tile.label"
96
+ :title="tile.count ? `${tile.label} (${tile.count})` : tile.label"
91
97
  :aria-pressed="active === tile.key"
92
98
  @click="emit('select', tile.key)"
93
99
  >
94
- <span class="rm-rail__tile"><i :class="`ph ph-${tile.icon}`" /></span>
100
+ <span class="rm-rail__tile">
101
+ <i :class="`ph ph-${tile.icon}`" />
102
+ <span v-if="tile.count" class="rm-rail__badge">{{ tile.count > 99 ? '99+' : tile.count }}</span>
103
+ </span>
95
104
  <span class="rm-rail__caption">{{ tile.label }}</span>
96
105
  </button>
97
106
 
@@ -1,6 +1,7 @@
1
1
  import { RM_EXPORT_EXTENSIONS } from "../utils/export.js";
2
2
  const BRAND_TITLE = "OIP Healthcare";
3
- const BRAND_LINE = "par Tekkare";
3
+ const BRAND_LINE = "By Tekkare \u{1F49C}";
4
+ const COVER_SHEET = "Tekkare";
4
5
  function longDate(lang) {
5
6
  return (/* @__PURE__ */ new Date()).toLocaleDateString(lang === "en" ? "en-US" : "fr-FR", {
6
7
  year: "numeric",
@@ -16,6 +17,19 @@ function columnsOf(sheet, keep) {
16
17
  const kept = sheet.schema.filter((column) => keep.includes(column.column));
17
18
  return kept.length ? kept : sheet.schema;
18
19
  }
20
+ const XLSX_CELL_MAX = 32767;
21
+ function xlsxSafeText(text) {
22
+ return text.length > XLSX_CELL_MAX ? `${text.slice(0, XLSX_CELL_MAX - 1)}\u2026` : text;
23
+ }
24
+ function xlsxColumnsOf(sheet, keep) {
25
+ return columnsOf(sheet, keep).map((column) => ({
26
+ ...column,
27
+ value: (row) => {
28
+ const value = column.value(row);
29
+ return typeof value === "string" ? xlsxSafeText(value) : value;
30
+ }
31
+ }));
32
+ }
19
33
  function cellText(value, lang) {
20
34
  if (value === null || value === void 0) return "";
21
35
  if (value instanceof Date) {
@@ -76,63 +90,101 @@ async function raster(markup, width, height, scale = 2) {
76
90
  function printable(text) {
77
91
  return text.replace(/[^\u0000-\u00ff]/g, "").replace(/\s{2,}/g, " ").trim();
78
92
  }
93
+ function cardsOf(payload, config, lang) {
94
+ if (payload.cards?.length) return payload.cards.slice(0, payload.cardLimit ?? 100);
95
+ const sheet = payload.sheets[0];
96
+ if (!sheet) return [];
97
+ const pick = (name) => name ? sheet.schema.find((column) => column.column === name) : void 0;
98
+ const title = pick(sheet.titleColumn);
99
+ const subtitle = pick(sheet.subtitleColumn);
100
+ const columns = columnsOf(sheet, config.columns);
101
+ const head = title ?? columns[0];
102
+ if (!head) return [];
103
+ const fields = columns.filter((column) => column !== head && column !== subtitle);
104
+ return rowsOf(sheet, payload.cardLimit ?? 100).map((row) => ({
105
+ title: cellText(head.value(row), lang) || "\u2014",
106
+ subtitle: subtitle ? cellText(subtitle.value(row), lang) : void 0,
107
+ fields: fields.map((column) => ({ label: column.column, value: cellText(column.value(row), lang) })).filter((field) => field.value !== "")
108
+ }));
109
+ }
79
110
  export function useRmExport() {
80
111
  function coverSheet(payload, config, lang) {
81
- const center = { align: "left", alignVertical: "center", fontFamily: "Figtree" };
112
+ const COLUMNS = 40;
113
+ const TEXT_COLUMN = 7;
114
+ const KPI_COLUMNS = [6, 8];
115
+ const base = {
116
+ borderColor: "#FFFFFF",
117
+ align: "center",
118
+ alignVertical: "center",
119
+ fontFamily: "Figtree"
120
+ };
121
+ const empty = { value: "", borderColor: "#FFFFFF" };
122
+ const filler = () => Array.from({ length: COLUMNS }, () => ({ value: "", borderColor: "#FFFFFF", width: 20, height: 30 }));
123
+ const valueStyle = { ...base, fontSize: 18, fontWeight: "bold" };
124
+ const labelStyle = { ...base, fontSize: 18, color: "#b2b2b2", fontStyle: "italic" };
82
125
  const rows = [];
83
- const line = (value, style = {}) => rows.push([{ ...center, value, ...style }]);
126
+ const spread = (cells) => rows.push(Array.from({ length: COLUMNS }, (_, index) => cells[index] ?? empty));
127
+ const line = (value, style = {}) => spread({ [TEXT_COLUMN]: { ...base, value: xlsxSafeText(String(value ?? "")), ...style } });
128
+ rows.push(filler(), filler(), filler());
84
129
  if (config.options.includes("logo")) {
85
- line(BRAND_TITLE, { fontSize: 36, fontWeight: "bold", height: 46 });
86
- line(BRAND_LINE, { fontSize: 14, color: "#6B3FFF" });
87
- rows.push([]);
88
- }
89
- line(payload.title, { fontSize: 20, fontWeight: "bold" });
90
- if (payload.subtitle) line(payload.subtitle, { fontSize: 13, color: "#6C6C8A" });
91
- line(
92
- lang === "en" ? `Generated on ${longDate(lang)}` : `G\xE9n\xE9r\xE9 le ${longDate(lang)}`,
93
- { fontSize: 11, color: "#6C6C8A" }
94
- );
95
- rows.push([]);
96
- if (payload.kpi?.length && config.sections.includes("kpi")) {
97
- line(lang === "en" ? "Key figures" : "Chiffres cl\xE9s", { fontSize: 13, fontWeight: "bold" });
98
- payload.kpi.forEach(
99
- (kpi) => rows.push([
100
- { ...center, value: kpi.label, fontSize: 11, color: "#6C6C8A", width: 34 },
101
- { ...center, value: String(kpi.value), fontSize: 11, fontWeight: "bold", width: 24 }
102
- ])
130
+ line(
131
+ lang === "en" ? `This document was generated on ${longDate(lang)} by` : `Ce document a \xE9t\xE9 g\xE9n\xE9r\xE9 le ${longDate(lang)} par`,
132
+ { fontSize: 16 }
103
133
  );
104
- rows.push([]);
134
+ line(BRAND_TITLE, { fontSize: 72, fontWeight: "bold", width: 60 });
135
+ line(BRAND_LINE, { fontSize: 24, width: 60 });
136
+ } else {
137
+ line(payload.title, { fontSize: 24, fontWeight: "bold" });
105
138
  }
106
- if (payload.filters?.length && config.sections.includes("filters")) {
107
- line(lang === "en" ? "Active filters" : "Filtres actifs", { fontSize: 13, fontWeight: "bold" });
108
- payload.filters.forEach(
109
- (filter) => rows.push([
110
- { ...center, value: filter.label, fontSize: 11, color: "#6C6C8A" },
111
- { ...center, value: filter.value, fontSize: 11 }
112
- ])
139
+ rows.push(filler());
140
+ const kpi = config.sections.includes("kpi") ? payload.kpi ?? [] : [];
141
+ if (kpi.length) {
142
+ line(lang === "en" ? "Your export currently contains :" : "Votre export contient :", { fontSize: 18 });
143
+ rows.push(filler());
144
+ for (let index = 0; index < kpi.length; index += 2) {
145
+ const pair = [kpi[index], kpi[index + 1]];
146
+ spread(
147
+ Object.fromEntries(
148
+ pair.map((item, side) => [
149
+ KPI_COLUMNS[side],
150
+ item ? { ...valueStyle, value: String(item.value) } : empty
151
+ ])
152
+ )
153
+ );
154
+ spread(
155
+ Object.fromEntries(
156
+ pair.map((item, side) => [
157
+ KPI_COLUMNS[side],
158
+ item ? { ...labelStyle, value: String(item.label) } : empty
159
+ ])
160
+ )
161
+ );
162
+ rows.push(filler());
163
+ }
164
+ }
165
+ if (config.options.includes("logo")) line(payload.title, { fontSize: 20, fontWeight: "bold" });
166
+ if (payload.subtitle) line(payload.subtitle, { fontSize: 16 });
167
+ const filters = config.sections.includes("filters") ? payload.filters ?? [] : [];
168
+ if (filters.length) {
169
+ rows.push(filler());
170
+ filters.forEach(
171
+ (filter) => line(`${filter.label} : ${filter.value}`, { fontSize: 16, color: "#b2b2b2" })
113
172
  );
114
- rows.push([]);
115
173
  }
116
174
  const withSources = config.sections.includes("sources") || config.options.includes("sources");
117
175
  if (payload.sources?.length && withSources) {
118
- line(lang === "en" ? "Sources" : "Sources", { fontSize: 13, fontWeight: "bold" });
176
+ rows.push(filler());
119
177
  payload.sources.forEach((source) => {
120
178
  const date = source.date ? new Date(source.date).toLocaleDateString(lang === "en" ? "en-US" : "fr-FR", {
121
179
  year: "numeric",
122
180
  month: "long",
123
181
  day: "numeric"
124
182
  }) : null;
125
- rows.push([
126
- { ...center, value: `${source.emoji ? `${source.emoji} ` : ""}${source.name}`, fontSize: 11 },
127
- {
128
- ...center,
129
- value: date ? lang === "en" ? `Last update: ${date}` : `Derni\xE8re mise \xE0 jour : ${date}` : "",
130
- fontSize: 11,
131
- color: "#6C6C8A"
132
- }
133
- ]);
183
+ const update = date ? lang === "en" ? ` - Last update: ${date}` : ` - Derni\xE8re mise \xE0 jour : ${date}` : "";
184
+ line(`${source.emoji ? `${source.emoji} ` : ""}Source : ${source.name}${update}`, { fontSize: 18 });
134
185
  });
135
186
  }
187
+ for (let index = 0; index < 15; index += 1) rows.push(filler());
136
188
  return rows;
137
189
  }
138
190
  async function toXlsx(config, payload, lang) {
@@ -141,13 +193,13 @@ export function useRmExport() {
141
193
  const sheets = payload.sheets;
142
194
  const data = [coverSheet(payload, config, lang)];
143
195
  const schema = [null];
144
- const names = [BRAND_TITLE];
196
+ const names = [COVER_SHEET];
145
197
  let rowCount = 0;
146
198
  sheets.forEach((sheet) => {
147
199
  const rows = rowsOf(sheet, payload.limit);
148
200
  rowCount += rows.length;
149
201
  data.push(rows);
150
- schema.push(columnsOf(sheet, config.columns));
202
+ schema.push(xlsxColumnsOf(sheet, config.columns));
151
203
  names.push(sheet.name.replace(/[[\]:*?/\\]/g, " ").slice(0, 31));
152
204
  });
153
205
  await writeXlsxFile(data, { schema, sheets: names, fileName, stickyRowsCount: 1 });
@@ -234,7 +286,12 @@ export function useRmExport() {
234
286
  const { default: JsPdf } = await import("jspdf");
235
287
  const { default: autoTable } = await import("jspdf-autotable");
236
288
  const landscape = config.layout === "a4-landscape";
237
- const doc = new JsPdf({ orientation: landscape ? "landscape" : "portrait", unit: "mm", format: "a4" });
289
+ const doc = new JsPdf({
290
+ orientation: landscape ? "landscape" : "portrait",
291
+ unit: "mm",
292
+ format: "a4",
293
+ compress: true
294
+ });
238
295
  const pageWidth = doc.internal.pageSize.getWidth();
239
296
  const margin = 14;
240
297
  const inner = pageWidth - margin * 2;
@@ -248,10 +305,10 @@ export function useRmExport() {
248
305
  doc.setTextColor(255, 255, 255);
249
306
  doc.setFont("helvetica", "bold");
250
307
  doc.setFontSize(22);
251
- doc.text(BRAND_TITLE, margin, 20);
308
+ doc.text(printable(BRAND_TITLE), margin, 20);
252
309
  doc.setFont("helvetica", "normal");
253
310
  doc.setFontSize(11);
254
- doc.text(BRAND_LINE, margin, 28);
311
+ doc.text(printable(BRAND_LINE), margin, 28);
255
312
  doc.setFontSize(9);
256
313
  doc.text(longDate(lang), pageWidth - margin, 28, { align: "right" });
257
314
  if (payload.logoDataUrl) {
@@ -332,47 +389,134 @@ export function useRmExport() {
332
389
  doc.addImage(canvas.toDataURL("image/png"), "PNG", margin, y, drawWidth, drawHeight, void 0, "FAST");
333
390
  y += drawHeight + 6;
334
391
  }
392
+ let rowCount = 0;
393
+ if (config.sections.includes("cards")) {
394
+ const cards = cardsOf(payload, config, lang);
395
+ rowCount = cards.length;
396
+ const labelWidth = Math.min(46, inner * 0.34);
397
+ const valueWidth = inner - labelWidth - 8;
398
+ const pageHeight = doc.internal.pageSize.getHeight();
399
+ cards.forEach((card) => {
400
+ const titleLines = doc.splitTextToSize(printable(card.title), inner - 8);
401
+ const subtitleLines = card.subtitle ? doc.splitTextToSize(printable(card.subtitle), inner - 8) : [];
402
+ const fields = card.fields.map((field) => ({
403
+ label: doc.splitTextToSize(printable(field.label), labelWidth),
404
+ value: doc.splitTextToSize(printable(field.value), valueWidth)
405
+ }));
406
+ const height = 6 + titleLines.length * 5 + (subtitleLines.length ? subtitleLines.length * 4 + 1 : 0) + (card.badges?.length ? 6 : 0) + fields.reduce((total2, field) => total2 + Math.max(field.label.length, field.value.length) * 4.2, 0) + 5;
407
+ if (y + height > pageHeight - margin) {
408
+ doc.addPage();
409
+ y = margin;
410
+ }
411
+ doc.setDrawColor(232, 232, 240);
412
+ doc.setFillColor(252, 252, 254);
413
+ doc.roundedRect(margin, y, inner, height, 2, 2, "FD");
414
+ let cursor = y + 6;
415
+ doc.setTextColor(...ink);
416
+ doc.setFont("helvetica", "bold");
417
+ doc.setFontSize(11);
418
+ doc.text(titleLines, margin + 4, cursor);
419
+ cursor += titleLines.length * 5;
420
+ if (subtitleLines.length) {
421
+ doc.setTextColor(...grey);
422
+ doc.setFont("helvetica", "normal");
423
+ doc.setFontSize(9);
424
+ doc.text(subtitleLines, margin + 4, cursor + 1);
425
+ cursor += subtitleLines.length * 4 + 1;
426
+ }
427
+ if (card.badges?.length) {
428
+ let badgeX = margin + 4;
429
+ doc.setFontSize(7);
430
+ card.badges.forEach((badge) => {
431
+ const text = printable(badge);
432
+ const width = doc.getTextWidth(text) + 4;
433
+ doc.setFillColor(240, 238, 255);
434
+ doc.roundedRect(badgeX, cursor - 2, width, 5, 1.2, 1.2, "F");
435
+ doc.setTextColor(...violet);
436
+ doc.text(text, badgeX + 2, cursor + 1.6);
437
+ badgeX += width + 2;
438
+ });
439
+ cursor += 6;
440
+ }
441
+ fields.forEach((field) => {
442
+ const lines = Math.max(field.label.length, field.value.length);
443
+ doc.setTextColor(...grey);
444
+ doc.setFont("helvetica", "bold");
445
+ doc.setFontSize(7);
446
+ doc.text(field.label, margin + 4, cursor + 1);
447
+ doc.setTextColor(...ink);
448
+ doc.setFont("helvetica", "normal");
449
+ doc.setFontSize(9);
450
+ doc.text(field.value, margin + 4 + labelWidth, cursor + 1);
451
+ cursor += lines * 4.2;
452
+ });
453
+ y += height + 4;
454
+ });
455
+ const total = payload.sheets[0]?.data.length ?? 0;
456
+ if (total > cards.length) {
457
+ doc.setTextColor(...grey);
458
+ doc.setFont("helvetica", "italic");
459
+ doc.setFontSize(8);
460
+ doc.text(
461
+ printable(
462
+ lang === "en" ? `${cards.length} of ${total} records shown. Export to Excel for the full set.` : `${cards.length} fiches sur ${total}. Passez par l'export Excel pour la totalit\xE9.`
463
+ ),
464
+ margin,
465
+ y + 2
466
+ );
467
+ y += 6;
468
+ }
469
+ }
335
470
  if (config.sections.includes("table")) {
336
- let rowCount = 0;
471
+ const MAX_COLUMNS = 8;
337
472
  payload.sheets.forEach((sheet) => {
338
- const columns = columnsOf(sheet, config.columns);
473
+ const all = columnsOf(sheet, config.columns);
474
+ const columns = all.slice(0, MAX_COLUMNS);
339
475
  const rows = rowsOf(sheet, payload.limit);
340
- rowCount += rows.length;
476
+ if (!config.sections.includes("cards")) rowCount += rows.length;
477
+ doc.addPage("a4", "landscape");
478
+ const wide = doc.internal.pageSize.getWidth() - margin * 2;
479
+ doc.setTextColor(...ink);
480
+ doc.setFont("helvetica", "bold");
481
+ doc.setFontSize(11);
482
+ doc.text(printable(sheet.name), margin, margin + 2);
483
+ if (all.length > columns.length) {
484
+ doc.setFont("helvetica", "italic");
485
+ doc.setFontSize(8);
486
+ doc.setTextColor(...grey);
487
+ doc.text(
488
+ printable(
489
+ lang === "en" ? `${columns.length} of ${all.length} columns. The Excel export carries them all.` : `${columns.length} colonnes sur ${all.length}. L'export Excel les porte toutes.`
490
+ ),
491
+ margin,
492
+ margin + 7
493
+ );
494
+ }
341
495
  autoTable(doc, {
342
- startY: y + 2,
496
+ startY: margin + 10,
343
497
  head: [columns.map((column) => printable(column.column))],
344
498
  body: rows.map((row) => columns.map((column) => printable(cellText(column.value(row), lang)))),
345
- styles: { fontSize: 7, cellPadding: 1.6, overflow: "linebreak", textColor: ink },
346
- headStyles: { fillColor: violet, textColor: [255, 255, 255], fontStyle: "bold" },
499
+ styles: { fontSize: 6.5, cellPadding: 1.4, overflow: "linebreak", textColor: ink, minCellWidth: wide / columns.length / 2 },
500
+ headStyles: { fillColor: violet, textColor: [255, 255, 255], fontStyle: "bold", valign: "middle" },
347
501
  alternateRowStyles: { fillColor: [250, 250, 253] },
348
- margin: { left: margin, right: margin },
349
- didDrawPage: () => {
350
- if (payload.sheets.length > 1) {
351
- doc.setTextColor(...grey);
352
- doc.setFont("helvetica", "bold");
353
- doc.setFontSize(8);
354
- doc.text(printable(sheet.name), margin, margin - 4);
355
- }
356
- }
502
+ margin: { left: margin, right: margin, top: margin, bottom: margin },
503
+ tableWidth: wide
357
504
  });
358
- y = doc.lastAutoTable?.finalY ?? y;
505
+ y = doc.lastAutoTable?.finalY ?? margin;
359
506
  });
360
- const withSources = config.sections.includes("sources") || config.options.includes("sources");
361
- if (payload.sources?.length && withSources) {
362
- doc.setTextColor(...grey);
363
- doc.setFont("helvetica", "normal");
364
- doc.setFontSize(8);
365
- const names = payload.sources.map((source) => `${source.name}${source.date ? ` (${new Date(source.date).toLocaleDateString(lang === "en" ? "en-US" : "fr-FR", { year: "numeric", month: "long", day: "numeric" })})` : ""}`).join(" \xB7 ");
366
- const lines = doc.splitTextToSize(printable(`Sources : ${names}`), inner);
367
- doc.text(lines, margin, Math.min(y + 8, doc.internal.pageSize.getHeight() - margin));
368
- }
369
- const fileName2 = `${config.fileName}.pdf`;
370
- const blob2 = doc.output("blob");
371
- return { fileName: fileName2, rows: rowCount, bytes: download(blob2, fileName2) };
507
+ }
508
+ const withSources = config.sections.includes("sources") || config.options.includes("sources");
509
+ if (payload.sources?.length && withSources) {
510
+ doc.setTextColor(...grey);
511
+ doc.setFont("helvetica", "normal");
512
+ doc.setFontSize(8);
513
+ const names = payload.sources.map((source) => `${source.name}${source.date ? ` (${new Date(source.date).toLocaleDateString(lang === "en" ? "en-US" : "fr-FR", { year: "numeric", month: "long", day: "numeric" })})` : ""}`).join(" \xB7 ");
514
+ const lines = doc.splitTextToSize(printable(`Sources : ${names}`), inner);
515
+ doc.text(lines, margin, Math.min(y + 8, doc.internal.pageSize.getHeight() - margin));
372
516
  }
373
517
  const fileName = `${config.fileName}.pdf`;
374
518
  const blob = doc.output("blob");
375
- return { fileName, rows: 0, bytes: download(blob, fileName) };
519
+ return { fileName, rows: rowCount, bytes: download(blob, fileName) };
376
520
  }
377
521
  async function toPermalink(payload) {
378
522
  const url = payload.permalink || (typeof window !== "undefined" ? window.location.href : "");
@@ -27,6 +27,15 @@ export interface RmExportSheet {
27
27
  name: string;
28
28
  schema: RmExportColumn[];
29
29
  data: any[];
30
+ /**
31
+ * Colonne qui identifie une ligne, par son intitule. Elle sert de titre aux
32
+ * fiches du rapport PDF. Sans elle, le titre retombe sur la premiere colonne
33
+ * du schema, qui est souvent la date : une fiche titree « 12 mars 2025 » ne
34
+ * dit pas de quel produit elle parle.
35
+ */
36
+ titleColumn?: string;
37
+ /** Colonne posee sous le titre de la fiche (le laboratoire, la date...). */
38
+ subtitleColumn?: string;
30
39
  }
31
40
  /** Chiffre cle repris sur la page de garde. */
32
41
  export interface RmExportKpi {
@@ -44,6 +53,21 @@ export interface RmExportFilter {
44
53
  label: string;
45
54
  value: string;
46
55
  }
56
+ /**
57
+ * Une fiche : ce que la page montre sous forme de carte, rendu tel quel dans
58
+ * le rapport PDF. Une page qui n'en declare pas en obtient quand meme, les
59
+ * fiches etant alors derivees des lignes du tableau (cf. useRmExport).
60
+ */
61
+ export interface RmExportCard {
62
+ title: string;
63
+ subtitle?: string;
64
+ /** Etiquettes courtes posees sous le titre (SMR, statut, pays...). */
65
+ badges?: string[];
66
+ fields: {
67
+ label: string;
68
+ value: string;
69
+ }[];
70
+ }
47
71
  /**
48
72
  * Un format d'export.
49
73
  *
@@ -64,15 +88,25 @@ export interface RmExportFormat {
64
88
  tabular?: boolean;
65
89
  /** Le format rend les graphiques. */
66
90
  visual?: boolean;
67
- /** Le format est mis en page : le choix A4 s'applique. */
91
+ /** Le format est mis en page : choix A4 et fiches detaillees. */
68
92
  paged?: boolean;
93
+ /**
94
+ * Le format porte un en-tete de document : page de garde, chiffres cles,
95
+ * filtres actifs, sources. Vrai pour le PDF et le classeur, faux pour un
96
+ * CSV ou une image, qui ne savent porter que leur contenu.
97
+ */
98
+ document?: boolean;
69
99
  /** Format annonce mais pas encore livre : la carte reste visible, grisee. */
70
100
  disabled?: boolean;
71
101
  /** Raison affichee sur une carte grisee. */
72
102
  disabledHint?: string;
73
103
  }
74
- /** Nature d'une section : elle decide de quels formats la section releve. */
75
- export type RmExportSectionKind = 'table' | 'visual' | 'meta';
104
+ /**
105
+ * Nature d'une section : elle decide de quels formats la section releve.
106
+ * `cards` ne vaut que pour un format mis en page, `meta` que pour un format
107
+ * qui porte un en-tete de document.
108
+ */
109
+ export type RmExportSectionKind = 'table' | 'visual' | 'meta' | 'cards';
76
110
  /** Section proposee a l'etape « Donnees ». */
77
111
  export interface RmExportSection {
78
112
  key: string;
@@ -142,6 +176,17 @@ export interface RmExportPayload {
142
176
  sources?: RmExportSource[];
143
177
  filters?: RmExportFilter[];
144
178
  sheets: RmExportSheet[];
179
+ /**
180
+ * Fiches du rapport. Absentes, elles sont derivees des lignes du premier
181
+ * onglet : le titre vient de la premiere colonne, les champs des suivantes.
182
+ */
183
+ cards?: RmExportCard[];
184
+ /**
185
+ * Plafond de fiches dans un rapport. Une fiche fait une dizaine de lignes :
186
+ * mille resultats donneraient un PDF de plusieurs centaines de pages, que
187
+ * personne ne lit et qu'aucune messagerie n'accepte.
188
+ */
189
+ cardLimit?: number;
145
190
  /**
146
191
  * Graphiques a rasteriser ou vectoriser. Les charts Romulus sont rendus par
147
192
  * ECharts en SVG : on recupere le noeud, pas une capture d'ecran.
@@ -12,10 +12,11 @@ export const RM_EXPORT_FORMATS = [
12
12
  label: "PDF",
13
13
  icon: "FilePdf",
14
14
  color: "var(--color-danger)",
15
- desc: "Rapport mis en page, pr\xEAt \xE0 partager. KPI, graphiques, table, en-t\xEAte OIP Healthcare.",
15
+ desc: "Rapport mis en page, pr\xEAt \xE0 partager. Chiffres cl\xE9s, fiches, graphiques, en-t\xEAte OIP Healthcare.",
16
16
  tabular: true,
17
17
  visual: true,
18
- paged: true
18
+ paged: true,
19
+ document: true
19
20
  },
20
21
  {
21
22
  key: "xlsx",
@@ -24,7 +25,7 @@ export const RM_EXPORT_FORMATS = [
24
25
  color: "var(--color-success)",
25
26
  desc: "Donn\xE9es brutes avec page de garde. Pour analyse en local ou consolidation.",
26
27
  tabular: true,
27
- paged: true
28
+ document: true
28
29
  },
29
30
  {
30
31
  key: "csv",
@@ -61,7 +62,11 @@ export const RM_EXPORT_FORMATS = [
61
62
  export const RM_EXPORT_SECTIONS = [
62
63
  { key: "kpi", label: "Chiffres cl\xE9s", kind: "meta", on: true },
63
64
  { key: "charts", label: "Graphiques", kind: "visual", on: true },
64
- { key: "table", label: "Tableau", kind: "table", on: true },
65
+ { key: "cards", label: "Fiches d\xE9taill\xE9es", kind: "cards", on: true },
66
+ // Decochee : dans un rapport, les fiches disent la meme chose en lisible.
67
+ // La case reste offerte a qui veut le tableau en annexe. Elle ne concerne
68
+ // que le PDF — les onglets du classeur ne dependent pas d'elle.
69
+ { key: "table", label: "Tableau r\xE9capitulatif", kind: "table" },
65
70
  { key: "filters", label: "Filtres actifs", kind: "meta", on: true },
66
71
  { key: "sources", label: "Sources et mises \xE0 jour", kind: "meta", on: true }
67
72
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",