@tekkare/romulus 0.1.31 → 0.1.33

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.30",
7
+ "version": "0.1.32",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -1 +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}.pane-title{color:var(--text-primary);font-family:var(--font-serif);font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.1;margin:0 0 16px}.pane-sub{color:var(--text-muted);font-size:13px;line-height:1.55;margin:0 0 24px;max-width:78ch}.chart-section{margin-inline:auto;max-width:var(--content-max-w,1440px);padding:0 0 40px;width:100%}.section-label{align-items:center;color:var(--text-secondary);display:flex;font-size:11px;font-weight:700;gap:8px;letter-spacing:.12em;margin:40px 0 18px;text-transform:uppercase}body.text-large .pane-title{font-size:38px}body.text-xl .pane-title{font-size:46px}body.text-large .section-label{font-size:13px}body.text-xl .section-label{font-size:14px}.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)}body.rm-switching,body.rm-switching *,body.rm-switching :after,body.rm-switching :before,body.theme-switching,body.theme-switching *,body.theme-switching :after,body.theme-switching :before{transition:none!important}body.high-contrast *{box-shadow:none!important}
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}.pane-title{color:var(--text-primary);font-family:var(--font-serif);font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.1;margin:0 0 16px}.pane-sub{color:var(--text-muted);font-size:13px;line-height:1.55;margin:0 0 24px;max-width:78ch}.chart-section{margin-inline:auto;max-width:var(--content-max-w,1440px);padding:0 0 40px;width:100%}.section-label{align-items:center;color:var(--text-secondary);display:flex;font-size:11px;font-weight:700;gap:8px;letter-spacing:.12em;margin:40px 0 18px;text-transform:uppercase}body.text-large .pane-title{font-size:38px}body.text-xl .pane-title{font-size:46px}body.text-large .section-label{font-size:13px}body.text-xl .section-label{font-size:14px}.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-body>.rm-empty{box-sizing:border-box;gap:6px;justify-content:center;max-width:none;min-height:240px;padding:36px 24px}.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}.tk-skeleton{height:14px;width:80%}.tk-empty{color:var(--text-muted);text-align:center}.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)}body.rm-switching,body.rm-switching *,body.rm-switching :after,body.rm-switching :before,body.theme-switching,body.theme-switching *,body.theme-switching :after,body.theme-switching :before{transition:none!important}body.high-contrast *{box-shadow:none!important}.rm-skeleton{animation:rm-skeleton-shimmer 1.5s ease-in-out infinite;background:linear-gradient(90deg,var(--bg-hover) 0,var(--bg-surface-2) 50%,var(--bg-hover) 100%);background-size:200% 100%;border-radius:var(--radius-md)}@keyframes rm-skeleton-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@media (prefers-reduced-motion:reduce){.rm-skeleton{animation:none;background:var(--bg-hover)}}.rm-state-loading{display:flex;flex-direction:column;gap:10px;justify-content:center;min-height:240px;padding:8px 4px}.rm-spinner{animation:rm-spinner-turn .8s linear infinite;border:3px solid var(--border-default);border-radius:50%;border-top-color:var(--color-secondary);height:32px;width:32px}@keyframes rm-spinner-turn{to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){.rm-spinner{animation-duration:3s}}.rm-state{color:var(--text-secondary);flex-direction:column;font-family:var(--font-sans);gap:6px;min-height:240px;padding:36px 24px;text-align:center}.rm-state,.rm-state__icon{align-items:center;display:flex;justify-content:center}.rm-state__icon{background:var(--bg-surface-3);border-radius:50%;color:var(--text-muted);height:56px;margin-bottom:6px;width:56px}.rm-state__title{color:var(--text-primary);font-size:var(--font-14);font-weight:var(--weight-semibold);margin:0}.rm-state__text{color:var(--text-muted);font-size:var(--font-12);line-height:1.5;margin:0;max-width:280px}.rm-state__action{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-12);font-weight:var(--weight-semibold);gap:6px;margin-top:10px;padding:6px 14px}.rm-state__action:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-state--error .rm-state__icon{background:var(--color-danger-bg);color:var(--color-danger)}.rm-state--error .rm-state__action{border-color:var(--color-danger);color:var(--color-danger)}.rm-state--error .rm-state__action:hover{background:var(--color-danger-bg)}
@@ -26,10 +26,26 @@ const props = withDefaults(defineProps<{
26
26
  extra?: string
27
27
  /** Masque le menu d'actions quand la carte n'a rien a exporter. */
28
28
  actions?: boolean
29
+ /**
30
+ * Etat de la donnee. Les trois variantes se posent dans le CORPS ; la tete
31
+ * et le pied restent stables, pour que le titre et la source restent sous
32
+ * les yeux pendant que la donnee charge, manque ou echoue.
33
+ */
34
+ state?: 'ready' | 'loading' | 'empty' | 'error'
35
+ /**
36
+ * Libelle du bouton du corps en erreur, qui emet `retry`. Vide : aucun
37
+ * bouton. Un bouton que la page n'ecoute pas ne repare rien, il ment.
38
+ */
39
+ retryLabel?: string
40
+ /** Libelle du bouton du corps vide, qui emet `emptyAction`. Vide : aucun bouton. */
41
+ emptyActionLabel?: string
29
42
  }>(), {
30
43
  context: undefined,
31
44
  extra: undefined,
32
45
  actions: true,
46
+ state: 'ready',
47
+ retryLabel: '',
48
+ emptyActionLabel: '',
33
49
  })
34
50
 
35
51
  const emit = defineEmits<{
@@ -39,6 +55,10 @@ const emit = defineEmits<{
39
55
  exportXlsx: []
40
56
  copyLink: []
41
57
  fullscreen: []
58
+ /** L'utilisateur relance la donnee depuis le corps en erreur. */
59
+ retry: []
60
+ /** L'utilisateur veut sortir du corps vide (elargir ses filtres). */
61
+ emptyAction: []
42
62
  }>()
43
63
 
44
64
  const menuOpen = ref(false)
@@ -116,8 +136,32 @@ function run(action: 'exportPng' | 'exportSvg' | 'exportCsv' | 'exportXlsx' | 'c
116
136
  </div>
117
137
  </div>
118
138
 
139
+ <!-- Un corps vide n'est jamais laisse blanc : chaque etat a son rendu par
140
+ defaut, qu'un slot dedie peut remplacer quand la page a mieux a dire. -->
119
141
  <div class="chart-card-body">
120
- <slot />
142
+ <slot v-if="state === 'ready'" />
143
+ <slot v-else-if="state === 'loading'" name="loading">
144
+ <RmLoadingState />
145
+ </slot>
146
+ <!-- Pas de cadre pointille ici : dans un corps de carte, la carte porte
147
+ deja son propre cadre. Le pointille est reserve aux vues entieres. -->
148
+ <slot v-else-if="state === 'empty'" name="empty">
149
+ <RmEmptyState
150
+ 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."
153
+ :action-label="emptyActionLabel"
154
+ action-icon="Broom"
155
+ @action="emit('emptyAction')"
156
+ />
157
+ </slot>
158
+ <slot v-else name="error">
159
+ <RmErrorState
160
+ text="La donnée de cette carte n'a pas pu être chargée."
161
+ :action-label="retryLabel"
162
+ @action="emit('retry')"
163
+ />
164
+ </slot>
121
165
  </div>
122
166
 
123
167
  <div class="chart-card-foot">
@@ -35,11 +35,17 @@ const props = withDefaults(defineProps<{
35
35
  sortDir?: 'asc' | 'desc'
36
36
  /** Message affiche quand il n'y a aucune ligne. Le hors-perimetre se dit, il ne s'estime pas. */
37
37
  emptyText?: string
38
+ /** Squelette de lignes pendant le chargement, plutot qu'un tableau vide. */
39
+ loading?: boolean
40
+ /** Nombre de lignes du squelette : la table garde sa hauteur a l'arrivee de la donnee. */
41
+ loadingRows?: number
38
42
  }>(), {
39
43
  captionVisible: false,
40
44
  sortKey: undefined,
41
45
  sortDir: undefined,
42
46
  emptyText: 'Aucune donnée disponible pour cette sélection.',
47
+ loading: false,
48
+ loadingRows: 5,
43
49
  })
44
50
 
45
51
  const emit = defineEmits<{ sort: [key: string] }>()
@@ -72,9 +78,18 @@ function ariaSort(column: RmColumn) {
72
78
  </th>
73
79
  </tr>
74
80
  </thead>
75
- <tbody>
81
+ <tbody v-if="loading">
82
+ <!-- Une table vide pendant le chargement se lit comme une absence de
83
+ donnee : le squelette dit que la donnee arrive. -->
84
+ <tr v-for="index in loadingRows" :key="index">
85
+ <td v-for="column in columns" :key="column.key">
86
+ <div class="rm-skeleton tk-skeleton" />
87
+ </td>
88
+ </tr>
89
+ </tbody>
90
+ <tbody v-else>
76
91
  <tr v-if="!rows.length">
77
- <td :colspan="columns.length">{{ emptyText }}</td>
92
+ <td :colspan="columns.length" class="tk-empty">{{ emptyText }}</td>
78
93
  </tr>
79
94
  <tr v-for="(row, index) in rows" :key="index">
80
95
  <template v-for="column in columns" :key="column.key">
@@ -1,8 +1,20 @@
1
1
  <!--
2
- Etat vide d'une liste : une icone dans un disque, un titre, une phrase.
2
+ Etat vide d'une liste ou d'une vue : une icone dans un disque, un titre, une
3
+ phrase, et l'action a faire.
3
4
 
4
5
  Remplace `arg-errors` d'Auriga. Les textes viennent de la page : le
5
6
  hors-perimetre se dit, il ne s'estime pas.
7
+
8
+ Le systeme de design demande trois choses de plus qu'un simple message.
9
+ D'abord une action, sauf s'il n'y a vraiment rien a faire : un ecran vide
10
+ sans issue laisse l'utilisateur devant une impasse. Ensuite un ton sans
11
+ catastrophisme, « Aucun résultat » plutot que « Erreur ». Enfin un titre qui
12
+ dit ce qui manque et une phrase qui dit pourquoi ET quoi faire.
13
+
14
+ `tone` colore le disque selon la nature du vide : violet quand il reste
15
+ quelque chose a creer, rouge quand un droit bloque, neutre quand une
16
+ recherche ne ramene rien. `framed` pose le cadre pointille des etats
17
+ transitoires.
6
18
  -->
7
19
  <script setup lang="ts">
8
20
  withDefaults(
@@ -10,23 +22,49 @@ withDefaults(
10
22
  icon?: string
11
23
  title: string
12
24
  text?: string
25
+ /** Couleur du disque : `neutral` recherche vide, `primary` a creer, `danger` bloquant. */
26
+ tone?: 'neutral' | 'primary' | 'danger'
27
+ /** Libelle de l'action. Vide pour n'en poser aucune. */
28
+ actionLabel?: string
29
+ /** Icone de l'action. */
30
+ actionIcon?: string
31
+ /** Cadre pointille, pour signaler un etat transitoire. */
32
+ framed?: boolean
13
33
  }>(),
14
- { icon: 'MagnifyingGlass', text: '' },
34
+ {
35
+ icon: 'MagnifyingGlass',
36
+ text: '',
37
+ tone: 'neutral',
38
+ actionLabel: '',
39
+ actionIcon: '',
40
+ framed: false,
41
+ },
15
42
  )
43
+
44
+ const emit = defineEmits<{ action: [] }>()
16
45
  </script>
17
46
 
18
47
  <template>
19
- <div class="rm-empty">
48
+ <div class="rm-empty" :class="[`rm-empty--${tone}`, { 'rm-empty--framed': framed }]">
20
49
  <div class="rm-empty__icon">
21
- <RmIcon :name="icon" :size="44" />
50
+ <RmIcon :name="icon" :size="28" />
22
51
  </div>
23
52
  <div class="rm-empty__text">
24
53
  <p class="rm-empty__title">{{ title }}</p>
25
54
  <p v-if="text" class="rm-empty__descr">{{ text }}</p>
26
55
  </div>
56
+ <button
57
+ v-if="actionLabel"
58
+ type="button"
59
+ class="rm-empty__action"
60
+ @click="emit('action')"
61
+ >
62
+ <RmIcon v-if="actionIcon" :name="actionIcon" :size="14" />
63
+ {{ actionLabel }}
64
+ </button>
27
65
  </div>
28
66
  </template>
29
67
 
30
68
  <style scoped>
31
- .rm-empty{color:var(--text-primary);flex-direction:column;font-family:var(--font-sans);gap:24px;max-width:409px}.rm-empty,.rm-empty__icon{align-items:center;display:flex}.rm-empty__icon{background:var(--border-default);border-radius:50px;color:var(--text-secondary);justify-content:center;padding:10px}.rm-empty__text{align-items:center;align-self:stretch;display:flex;flex-direction:column;gap:8px;text-align:center}.rm-empty__title{font-size:var(--font-16);font-weight:var(--weight-bold);line-height:normal;margin:0}.rm-empty__descr{color:var(--text-secondary);font-size:var(--font-14);line-height:160%;margin:0}
69
+ .rm-empty{align-items:center;color:var(--text-primary);display:flex;flex-direction:column;font-family:var(--font-sans);gap:24px;max-width:409px}.rm-empty--framed{background:var(--bg-surface-2);border:1px dashed var(--border-default);border-radius:12px;box-sizing:border-box;max-width:none;padding:32px 16px;width:100%}.rm-empty__icon{align-items:center;background:var(--bg-surface-3);border-radius:50%;color:var(--text-muted);display:flex;flex:0 0 auto;height:56px;justify-content:center;width:56px}.rm-empty--primary .rm-empty__icon{background:var(--bg-hover);color:var(--color-secondary)}.rm-empty--danger .rm-empty__icon{background:var(--color-danger-bg);color:var(--color-danger)}.rm-empty__text{align-items:center;align-self:stretch;display:flex;flex-direction:column;gap:8px;text-align:center}.rm-empty__title{font-size:var(--font-16);font-weight:var(--weight-bold);line-height:normal;margin:0}.rm-empty__descr{color:var(--text-secondary);font-size:var(--font-14);line-height:160%;margin:0}.rm-empty__action{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-13);font-weight:var(--weight-semibold);gap:6px;padding:7px 14px}.rm-empty__action:hover{background:var(--bg-surface-3);color:var(--text-primary)}
32
70
  </style>
@@ -0,0 +1,69 @@
1
+ <!--
2
+ RmErrorState - corps de carte ou de vue dont la donnee a echoue. Style :
3
+ components.css (.rm-state, .rm-state--error).
4
+
5
+ Le systeme de design impose trois pieces d'information, dans cet ordre : ce
6
+ qui a echoue (`title`), pourquoi (`text`), et quoi faire (`actionLabel` avec
7
+ l'evenement `action`). Une erreur sans issue est une impasse, d'ou l'action
8
+ posee par defaut.
9
+
10
+ Le ton reste factuel : « Connecteur indisponible », pas « ERREUR FATALE ».
11
+ Les textes viennent donc de l'appelant, qui seul sait ce qui a echoue.
12
+
13
+ Pour une erreur serveur, `code` ajoute l'identifiant que le support
14
+ demandera. L'etat ne se signale jamais par le rouge seul : couleur, icone
15
+ et texte disent la meme chose, pour rester lisible en vision des couleurs
16
+ alteree.
17
+ -->
18
+ <script setup lang="ts">
19
+ withDefaults(
20
+ defineProps<{
21
+ /** Ce qui a echoue. */
22
+ title?: string
23
+ /** Pourquoi, et depuis quand. */
24
+ text?: string
25
+ /** Icone du disque : `CloudWarning` reseau, `LockKey` droits, `WarningOctagon` sinon. */
26
+ icon?: string
27
+ /** Libelle de l'action de recuperation. Vide pour aucune action. */
28
+ actionLabel?: string
29
+ /** Icone de l'action. */
30
+ actionIcon?: string
31
+ /** Identifiant a citer au support, pour une erreur serveur. */
32
+ code?: string
33
+ }>(),
34
+ {
35
+ title: 'Donnée indisponible',
36
+ text: '',
37
+ icon: 'WarningOctagon',
38
+ actionLabel: 'Réessayer',
39
+ actionIcon: 'ArrowClockwise',
40
+ code: '',
41
+ },
42
+ )
43
+
44
+ const emit = defineEmits<{ action: [] }>()
45
+ </script>
46
+
47
+ <template>
48
+ <div class="rm-state rm-state--error" role="alert">
49
+ <div class="rm-state__icon">
50
+ <RmIcon :name="icon" :size="26" />
51
+ </div>
52
+ <p class="rm-state__title">{{ title }}</p>
53
+ <p v-if="text" class="rm-state__text">{{ text }}</p>
54
+ <p v-if="code" class="rm-state__code">Code : {{ code }}</p>
55
+ <button
56
+ v-if="actionLabel"
57
+ type="button"
58
+ class="rm-state__action"
59
+ @click="emit('action')"
60
+ >
61
+ <RmIcon v-if="actionIcon" :name="actionIcon" :size="14" />
62
+ {{ actionLabel }}
63
+ </button>
64
+ </div>
65
+ </template>
66
+
67
+ <style scoped>
68
+ .rm-state__code{color:var(--text-muted);font-family:var(--font-mono);font-size:var(--font-11);margin:2px 0 0}
69
+ </style>
@@ -75,6 +75,26 @@ function toggle(which: RmFilterBarPanel) {
75
75
  panel.value = panel.value === which ? null : which
76
76
  }
77
77
 
78
+ /**
79
+ * Le lien de recherche avancee est pose DANS le champ, a son bout : le champ
80
+ * doit donc lui reserver sa place, sinon le texte saisi et la croix passent
81
+ * dessous. La largeur se mesure — « Recherche avancee » et « Advanced search »
82
+ * ne font pas la meme longueur.
83
+ */
84
+ const advancedLink = ref<HTMLElement | null>(null)
85
+ const advancedWidth = ref(0)
86
+ let advancedSize: ResizeObserver | null = null
87
+
88
+ function watchAdvanced() {
89
+ if (!advancedLink.value) return
90
+ advancedSize?.disconnect()
91
+ advancedSize = new ResizeObserver(() => {
92
+ advancedWidth.value = advancedLink.value?.offsetWidth ?? 0
93
+ })
94
+ advancedSize.observe(advancedLink.value)
95
+ advancedWidth.value = advancedLink.value.offsetWidth
96
+ }
97
+
78
98
  /** Onglets de vue publies par la page. */
79
99
  const { view } = useRmFilterBarView()
80
100
 
@@ -100,11 +120,16 @@ function watchHeader() {
100
120
  stickyTop.value = header.offsetHeight
101
121
  }
102
122
 
103
- onMounted(() => watchHeader())
123
+ onMounted(() => {
124
+ watchHeader()
125
+ watchAdvanced()
126
+ })
104
127
 
105
128
  onBeforeUnmount(() => {
106
129
  headerSize?.disconnect()
107
130
  headerSize = null
131
+ advancedSize?.disconnect()
132
+ advancedSize = null
108
133
  })
109
134
  </script>
110
135
 
@@ -113,7 +138,7 @@ onBeforeUnmount(() => {
113
138
  id="rm_filter_bar"
114
139
  class="rm-fbar"
115
140
  :class="panel ? 'rm-fbar--open' : 'rm-fbar--closed'"
116
- :style="{ top: `${stickyTop}px` }"
141
+ :style="{ top: `${stickyTop}px`, '--rm-fbar-advanced-w': `${advancedWidth}px` }"
117
142
  >
118
143
  <div class="rm-fbar__row">
119
144
  <div v-if="view" class="rm-fbar__view">
@@ -134,20 +159,21 @@ onBeforeUnmount(() => {
134
159
 
135
160
  <div class="rm-fbar__search">
136
161
  <slot name="Search" />
137
- </div>
138
162
 
139
- <!-- La recherche avancee s'ouvre par un lien pose au bout du champ : elle
140
- prolonge la recherche, elle n'est pas un troisieme reglage a cote de
141
- « Filtres » et « Personnaliser ». -->
142
- <button
143
- v-if="hasAdvanced"
144
- type="button"
145
- class="rm-fbar__advanced"
146
- :class="{ 'is-active': advancedOpen }"
147
- @click="toggle('advanced')"
148
- >
149
- {{ t.advanced }}
150
- </button>
163
+ <!-- La recherche avancee s'ouvre par un lien pose dans le champ, a son
164
+ bout : elle prolonge la recherche, elle n'est pas un troisieme
165
+ reglage a cote de « Filtres » et « Personnaliser ». -->
166
+ <button
167
+ v-if="hasAdvanced"
168
+ ref="advancedLink"
169
+ type="button"
170
+ class="rm-fbar__advanced"
171
+ :class="{ 'is-active': advancedOpen }"
172
+ @click="toggle('advanced')"
173
+ >
174
+ {{ t.advanced }}
175
+ </button>
176
+ </div>
151
177
 
152
178
  <button
153
179
  v-if="hasCustomize"
@@ -207,5 +233,5 @@ onBeforeUnmount(() => {
207
233
  </template>
208
234
 
209
235
  <style scoped>
210
- .rm-fbar{margin-inline:calc((min(var(--oip-content-max, 100%), 100%) - var(--oip-main-w, 100%))/2);position:sticky;width:var(--oip-main-w,100%);z-index:91;--rm-fbar-field-h:32px;backdrop-filter:blur(12px);background:rgba(245,245,249,.8);border-bottom:1px solid var(--border-default);display:flex;flex-direction:column;font-family:var(--font-sans)}.rm-fbar__row{align-items:flex-start;display:flex;gap:12px;padding-block:8px;padding-inline:max(32px,(100% - var(--oip-content-max,100%))/2 + 32px)}.rm-fbar--open .rm-fbar__row{border-bottom:1px solid var(--border-default)}.rm-fbar__toggle :deep(p),.rm-fbar__toggle :deep(span){font-size:inherit;font-weight:inherit;line-height:inherit;margin:0}.rm-fbar__toggle{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;display:flex;flex:0 0 auto;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h);justify-content:center;padding:0 14px}.rm-fbar__toggle:hover{background:var(--bg-surface-3)}.rm-fbar__toggle.is-active,.rm-fbar__toggle.is-active:hover{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.rm-fbar__toggle.is-active .rm-fbar__badge{background:var(--color-text-inverse);color:var(--color-primary)}.rm-fbar__badge{align-items:center;background:var(--color-primary);border-radius:16px;color:var(--color-text-inverse);display:inline-flex;font-size:var(--font-11);font-weight:var(--weight-bold);justify-content:center;min-width:18px;padding:0 5px}.rm-fbar__view{align-items:center;display:flex;flex:0 0 auto}.rm-fbar__advanced{align-items:center;background:none;border:none;color:var(--color-primary);cursor:pointer;display:inline-flex;flex:0 0 auto;font-family:inherit;font-size:var(--font-13);font-weight:var(--weight-medium);height:var(--rm-fbar-field-h);padding:0;text-decoration-line:underline;white-space:nowrap}.rm-fbar__advanced.is-active,.rm-fbar__advanced:hover{color:var(--color-primary-hover,var(--color-primary));text-decoration-thickness:2px}.rm-fbar__search{align-items:center;display:flex;flex:1 1 auto;gap:8px;min-width:0}.rm-fbar__search:empty{display:none}.rm-fbar__search :deep(.filters_input_search){border-radius:var(--radius-pill);box-sizing:border-box;height:auto;min-height:var(--rm-fbar-field-h);width:100%}.rm-fbar__search :deep(.research_tags:not(:has(*))){display:none}.rm-fbar__search :deep(.select_shortcut){box-sizing:border-box;font-size:var(--font-11);height:24px;padding-block:0}.rm-fbar__search :deep(.select_shortcut:hover){background:var(--bg-surface-3)!important;border-color:var(--border-default)!important;color:inherit!important}.rm-fbar__search :deep(.select_shortcut:hover svg){color:inherit!important}.rm-fbar__search :deep(.remove_search_section){border-left:none;border-radius:0 var(--radius-pill) var(--radius-pill) 0}.rm-fbar__search :deep(.remove_search_section:hover){background:transparent!important}.rm-fbar__search :deep(.remove_search_section:hover svg){color:var(--text-primary)!important}.rm-fbar__search :deep(.filters_input_search.no_bottom){border-bottom:1.5px solid var(--color-primary)!important;border-radius:var(--radius-pill)}.rm-fbar__search :deep(.search_suggestions){border:1px solid var(--border-default);border-radius:var(--radius-2xl);box-shadow:var(--shadow-lg);left:0;padding:6px;right:0;top:calc(100% + 6px);width:auto}.rm-fbar__search :deep(.sugg_container>*){border-radius:var(--radius-lg)}.rm-fbar__search :deep(.sugg_container>.selected){background:color-mix(in srgb,var(--color-primary) 6%,transparent)!important}.rm-fbar__panel{--rm-filter-col:224px;align-items:end;display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(0,var(--rm-filter-col)));justify-content:center;padding-block:16px 20px;padding-inline:56px}.rm-fbar__panel--wide{align-items:stretch;display:flex;flex-direction:column;gap:12px}.rm-fbar__filters{display:contents}.rm-fbar__panel-head{align-items:baseline;display:flex;gap:16px;grid-column:1/-1;width:100%}.rm-fbar__customize{grid-column:1/-1}.rm-fbar__panel :deep(.filters_row_break){grid-column:1/-1}.rm-fbar__panel-title{font-size:var(--font-18);font-weight:800;margin:0}.rm-fbar__action{background:none;border:none;cursor:pointer;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);padding:0;text-decoration-line:underline}.rm-fbar__action--save{color:var(--color-primary)}.rm-fbar__action--clear{color:var(--text-secondary)}
236
+ .rm-fbar{margin-inline:calc((min(var(--oip-content-max, 100%), 100%) - var(--oip-main-w, 100%))/2);position:sticky;width:var(--oip-main-w,100%);z-index:91;--rm-fbar-field-h:32px;backdrop-filter:blur(12px);background:rgba(245,245,249,.8);border-bottom:1px solid var(--border-default);display:flex;flex-direction:column;font-family:var(--font-sans)}.rm-fbar__row{align-items:flex-start;display:flex;gap:12px;padding-block:8px;padding-inline:max(32px,(100% - var(--oip-content-max,100%))/2 + 32px)}.rm-fbar--open .rm-fbar__row{border-bottom:1px solid var(--border-default)}.rm-fbar__toggle :deep(p),.rm-fbar__toggle :deep(span){font-size:inherit;font-weight:inherit;line-height:inherit;margin:0}.rm-fbar__toggle{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;display:flex;flex:0 0 auto;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h);justify-content:center;padding:0 14px}.rm-fbar__toggle:hover{background:var(--bg-surface-3)}.rm-fbar__toggle.is-active,.rm-fbar__toggle.is-active:hover{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.rm-fbar__toggle.is-active .rm-fbar__badge{background:var(--color-text-inverse);color:var(--color-primary)}.rm-fbar__badge{align-items:center;background:var(--color-primary);border-radius:16px;color:var(--color-text-inverse);display:inline-flex;font-size:var(--font-11);font-weight:var(--weight-bold);justify-content:center;min-width:18px;padding:0 5px}.rm-fbar__view{align-items:center;display:flex;flex:0 0 auto}.rm-fbar__advanced{align-items:center;background:none;border:none;border-left:1px solid var(--border-default);color:var(--color-primary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-13);font-weight:var(--weight-medium);height:var(--rm-fbar-field-h);padding:0 0 0 12px;position:absolute;right:14px;text-decoration-line:underline;top:0;white-space:nowrap;z-index:2}.rm-fbar__advanced.is-active,.rm-fbar__advanced:hover{color:var(--color-primary-hover,var(--color-primary));text-decoration-thickness:2px}.rm-fbar__search{align-items:center;display:flex;flex:1 1 auto;gap:8px;min-width:0;position:relative}.rm-fbar__search:empty{display:none}.rm-fbar__search :deep(.filters_input_search){border-radius:var(--radius-pill);box-sizing:border-box;height:auto;min-height:var(--rm-fbar-field-h);padding-right:calc(var(--rm-fbar-advanced-w, 0px) + 12px);width:100%}.rm-fbar__search :deep(.research_tags:not(:has(*))){display:none}.rm-fbar__search :deep(.select_shortcut){box-sizing:border-box;font-size:var(--font-11);height:24px;padding-block:0}.rm-fbar__search :deep(.select_shortcut:hover){background:var(--bg-surface-3)!important;border-color:var(--border-default)!important;color:inherit!important}.rm-fbar__search :deep(.select_shortcut:hover svg){color:inherit!important}.rm-fbar__search :deep(.remove_search_section){border-left:none;border-radius:0 var(--radius-pill) var(--radius-pill) 0}.rm-fbar__search :deep(.remove_search_section:hover){background:transparent!important}.rm-fbar__search :deep(.remove_search_section:hover svg){color:var(--text-primary)!important}.rm-fbar__search :deep(.filters_input_search.no_bottom){border-bottom:1.5px solid var(--color-primary)!important;border-radius:var(--radius-pill)}.rm-fbar__search :deep(.search_suggestions){border:1px solid var(--border-default);border-radius:var(--radius-2xl);box-shadow:var(--shadow-lg);left:0;padding:6px;right:0;top:calc(100% + 6px);width:auto}.rm-fbar__search :deep(.sugg_container>*){border-radius:var(--radius-lg)}.rm-fbar__search :deep(.sugg_container>.selected){background:color-mix(in srgb,var(--color-primary) 6%,transparent)!important}.rm-fbar__panel{--rm-filter-col:224px;align-items:end;display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(0,var(--rm-filter-col)));justify-content:center;padding-block:16px 20px;padding-inline:56px}.rm-fbar__panel--wide{align-items:stretch;display:flex;flex-direction:column;gap:12px}.rm-fbar__filters{display:contents}.rm-fbar__panel-head{align-items:baseline;display:flex;gap:16px;grid-column:1/-1;width:100%}.rm-fbar__customize{grid-column:1/-1}.rm-fbar__panel :deep(.filters_row_break){grid-column:1/-1}.rm-fbar__panel-title{font-size:var(--font-18);font-weight:800;margin:0}.rm-fbar__action{background:none;border:none;cursor:pointer;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);padding:0;text-decoration-line:underline}.rm-fbar__action--save{color:var(--color-primary)}.rm-fbar__action--clear{color:var(--text-secondary)}
211
237
  </style>
@@ -0,0 +1,81 @@
1
+ <!--
2
+ RmLoadingState - corps de carte en chargement. Style : components.css
3
+ (.rm-state-loading).
4
+
5
+ Le systeme de design tranche : squelette plutot que rotative des qu'on
6
+ connait la forme du contenu, et jamais de blanc pendant une attente. Ce
7
+ composant porte la forme la plus courante d'un corps de carte, un titre,
8
+ une plage de graphe, deux lignes de legende. `chart` a false donne une
9
+ simple pile de lignes, pour une liste ou un tableau.
10
+
11
+ Une rotative reste possible via `spinner`, pour une action ponctuelle dont
12
+ on ignore la structure du resultat.
13
+ -->
14
+ <script setup lang="ts">
15
+ import { computed } from 'vue'
16
+
17
+ const props = withDefaults(
18
+ defineProps<{
19
+ /** Pose la plage pleine du graphe entre les lignes. */
20
+ chart?: boolean
21
+ /** Nombre de lignes de texte simulees. */
22
+ lines?: number
23
+ /** Hauteur de la plage de graphe. */
24
+ chartHeight?: string | number
25
+ /** Rotative plutot que squelette, avec ce libelle. */
26
+ spinner?: boolean
27
+ /** Texte sous la rotative. */
28
+ label?: string
29
+ /** Hauteur minimale du bloc, pour epouser la carte qu'il remplit. */
30
+ minHeight?: string | number
31
+ }>(),
32
+ {
33
+ chart: true,
34
+ lines: 2,
35
+ chartHeight: 120,
36
+ spinner: false,
37
+ label: '',
38
+ minHeight: undefined,
39
+ },
40
+ )
41
+
42
+ /** Largeurs decroissantes : une pile de lignes egales ne ressemble a rien. */
43
+ const WIDTHS = ['90%', '70%', '80%', '60%', '75%']
44
+
45
+ const size = (value: string | number | undefined) =>
46
+ value === undefined ? undefined : typeof value === 'number' ? `${value}px` : value
47
+
48
+ const style = computed(() => ({ minHeight: size(props.minHeight) }))
49
+ </script>
50
+
51
+ <template>
52
+ <div
53
+ class="rm-state-loading"
54
+ :style="style"
55
+ role="status"
56
+ aria-busy="true"
57
+ :aria-label="label || 'Chargement en cours'"
58
+ >
59
+ <template v-if="spinner">
60
+ <div class="rm-loading__center">
61
+ <div class="rm-spinner" />
62
+ <p v-if="label" class="rm-loading__label">{{ label }}</p>
63
+ </div>
64
+ </template>
65
+
66
+ <template v-else>
67
+ <RmSkeleton width="50%" :height="14" />
68
+ <RmSkeleton v-if="chart" width="100%" :height="chartHeight" radius="8px" />
69
+ <RmSkeleton
70
+ v-for="index in lines"
71
+ :key="index"
72
+ :width="WIDTHS[(index - 1) % WIDTHS.length]"
73
+ :height="10"
74
+ />
75
+ </template>
76
+ </div>
77
+ </template>
78
+
79
+ <style scoped>
80
+ .rm-loading__center{align-items:center;display:flex;flex:1;flex-direction:column;gap:12px;justify-content:center}.rm-loading__label{color:var(--text-muted);font-family:var(--font-sans);font-size:var(--font-12);margin:0}
81
+ </style>
@@ -0,0 +1,32 @@
1
+ <!--
2
+ RmSkeleton - un bloc de squelette anime, pose a la place du contenu qui
3
+ charge. Style : components.css (.rm-skeleton).
4
+
5
+ Il n'a aucune forme propre : c'est l'appelant qui donne la largeur et la
6
+ hauteur du contenu remplace. Un squelette qui ne ressemble pas au contenu
7
+ final fait sauter la mise en page a l'arrivee de la donnee, ce que le
8
+ squelette est justement la pour eviter.
9
+ -->
10
+ <script setup lang="ts">
11
+ withDefaults(
12
+ defineProps<{
13
+ /** Largeur CSS. Un nombre est lu en pixels. */
14
+ width?: string | number
15
+ /** Hauteur CSS. Un nombre est lu en pixels. */
16
+ height?: string | number
17
+ /** Rayon CSS, pour un disque (`50%`) ou une pastille. */
18
+ radius?: string
19
+ }>(),
20
+ { width: '100%', height: 12, radius: undefined },
21
+ )
22
+
23
+ const size = (value: string | number) => (typeof value === 'number' ? `${value}px` : value)
24
+ </script>
25
+
26
+ <template>
27
+ <div
28
+ class="rm-skeleton"
29
+ aria-hidden="true"
30
+ :style="{ width: size(width), height: size(height), borderRadius: radius }"
31
+ />
32
+ </template>
@@ -117,7 +117,7 @@ const sortedRows = computed(() => {
117
117
  <tbody v-if="loading">
118
118
  <tr v-for="i in 5" :key="i">
119
119
  <td v-for="col in columns" :key="col.key" class="rm-table__td">
120
- <div class="rm-table__skeleton" />
120
+ <div class="rm-skeleton rm-table__skeleton" />
121
121
  </td>
122
122
  </tr>
123
123
  </tbody>
@@ -158,5 +158,5 @@ const sortedRows = computed(() => {
158
158
  </template>
159
159
 
160
160
  <style scoped>
161
- .rm-table-wrapper{background:#fff;border:1px solid #dbdef0;border-radius:8px;flex:1;overflow:auto}.rm-table{border-collapse:collapse;font-family:var(--r-font-family);font-size:var(--r-text-sm);width:100%}.rm-table__head{background:var(--r-color-gray-50)}.rm-table__head--sticky{position:sticky;top:0;z-index:2}.rm-table__th{border-bottom:1px solid #dbdef0;color:var(--r-color-gray-500);font-size:12px;font-weight:600;letter-spacing:.3px;padding:10px 16px;text-align:left;text-transform:uppercase;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.rm-table__th--center{text-align:center}.rm-table__th--right{text-align:right}.rm-table__th--sortable{cursor:pointer}.rm-table__th--sortable:hover{color:var(--r-color-gray-700)}.rm-table__th-content{align-items:center;display:inline-flex;gap:4px}.rm-table__sort{color:var(--r-color-gray-300);display:inline-flex;flex-direction:column;gap:-2px}.rm-table__sort--active{color:#4164ee}.rm-table__td{border-bottom:1px solid var(--r-color-gray-100);color:var(--r-color-gray-700);padding:10px 16px;vertical-align:top}.rm-table__td--center{text-align:center}.rm-table__td--right{text-align:right}.rm-table__row:last-child .rm-table__td{border-bottom:none}.rm-table__row--striped{background-color:var(--r-color-gray-50)}.rm-table__row--hoverable:hover{background-color:rgba(65,100,238,.06);cursor:pointer}.rm-table__empty{color:var(--r-color-gray-400);font-style:italic;padding:32px 16px;text-align:center}.rm-table__skeleton{animation:rm-table-shimmer 1.5s infinite;background:linear-gradient(90deg,#eee 25%,#e0e0e0 50%,#eee 75%);background-size:200% 100%;border-radius:4px;height:14px;width:80%}@keyframes rm-table-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}
161
+ .rm-table-wrapper{background:#fff;border:1px solid #dbdef0;border-radius:8px;flex:1;overflow:auto}.rm-table{border-collapse:collapse;font-family:var(--r-font-family);font-size:var(--r-text-sm);width:100%}.rm-table__head{background:var(--r-color-gray-50)}.rm-table__head--sticky{position:sticky;top:0;z-index:2}.rm-table__th{border-bottom:1px solid #dbdef0;color:var(--r-color-gray-500);font-size:12px;font-weight:600;letter-spacing:.3px;padding:10px 16px;text-align:left;text-transform:uppercase;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.rm-table__th--center{text-align:center}.rm-table__th--right{text-align:right}.rm-table__th--sortable{cursor:pointer}.rm-table__th--sortable:hover{color:var(--r-color-gray-700)}.rm-table__th-content{align-items:center;display:inline-flex;gap:4px}.rm-table__sort{color:var(--r-color-gray-300);display:inline-flex;flex-direction:column;gap:-2px}.rm-table__sort--active{color:#4164ee}.rm-table__td{border-bottom:1px solid var(--r-color-gray-100);color:var(--r-color-gray-700);padding:10px 16px;vertical-align:top}.rm-table__td--center{text-align:center}.rm-table__td--right{text-align:right}.rm-table__row:last-child .rm-table__td{border-bottom:none}.rm-table__row--striped{background-color:var(--r-color-gray-50)}.rm-table__row--hoverable:hover{background-color:rgba(65,100,238,.06);cursor:pointer}.rm-table__empty{color:var(--text-muted);padding:32px 16px;text-align:center}.rm-table__skeleton{height:14px;width:80%}
162
162
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",