@tekkare/romulus 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/assets/styles/components.css +1 -1
  3. package/dist/runtime/assets/styles/tokens.css +1 -1
  4. package/dist/runtime/components/ActionBar.vue +1 -1
  5. package/dist/runtime/components/AdvancedSearch.vue +29 -10
  6. package/dist/runtime/components/BarChart.vue +45 -0
  7. package/dist/runtime/components/Breadcrumb.vue +1 -1
  8. package/dist/runtime/components/Button.vue +1 -1
  9. package/dist/runtime/components/Card.vue +1 -1
  10. package/dist/runtime/components/Chart.vue +75 -0
  11. package/dist/runtime/components/ChartCard.vue +5 -1
  12. package/dist/runtime/components/DonutChart.vue +27 -0
  13. package/dist/runtime/components/Drawer.vue +1 -1
  14. package/dist/runtime/components/FilterCheckbox.vue +1 -1
  15. package/dist/runtime/components/FilterComplex.vue +3 -3
  16. package/dist/runtime/components/FilterDropdown.vue +26 -1
  17. package/dist/runtime/components/Input.vue +1 -1
  18. package/dist/runtime/components/LineChart.vue +42 -0
  19. package/dist/runtime/components/NavLink.vue +1 -1
  20. package/dist/runtime/components/Pill.vue +1 -1
  21. package/dist/runtime/components/PillTabs.vue +1 -1
  22. package/dist/runtime/components/SearchBar.vue +103 -19
  23. package/dist/runtime/components/Sidebar.vue +1 -1
  24. package/dist/runtime/components/SparklineChart.vue +33 -0
  25. package/dist/runtime/components/Tabs.vue +5 -5
  26. package/dist/runtime/components/ToolbarFilter.vue +1 -1
  27. package/dist/runtime/composables/useChartTheme.d.ts +7 -0
  28. package/dist/runtime/composables/useChartTheme.js +27 -0
  29. package/dist/runtime/composables/useTheme.d.ts +17 -0
  30. package/dist/runtime/composables/useTheme.js +98 -0
  31. package/dist/runtime/utils/chart.d.ts +475 -0
  32. package/dist/runtime/utils/chart.js +209 -0
  33. package/package.json +3 -2
@@ -0,0 +1,42 @@
1
+ <!--
2
+ RmLineChart - courbe, aire, ou marche d'escalier (`step`) pour les series de
3
+ prix et de decisions discretes.
4
+ -->
5
+ <script setup lang="ts">
6
+ import { computed } from 'vue'
7
+ import { useChartTheme } from '../composables/useChartTheme'
8
+ import { rmLineOption, type RmSeriesInput, RM_CHART_HEIGHTS } from '../utils/chart'
9
+
10
+ const props = withDefaults(defineProps<{
11
+ categories: (string | number)[]
12
+ series: RmSeriesInput[]
13
+ /** Remplissage degrade sous la courbe. */
14
+ area?: boolean
15
+ /** Marche d'escalier : prix, taux de remboursement, decisions datees. */
16
+ step?: boolean
17
+ smooth?: boolean
18
+ height?: keyof typeof RM_CHART_HEIGHTS | number
19
+ description?: string
20
+ }>(), {
21
+ area: false,
22
+ step: false,
23
+ smooth: true,
24
+ height: 'standard',
25
+ description: undefined,
26
+ })
27
+
28
+ const { theme, palette, decal } = useChartTheme()
29
+
30
+ const option = computed(() =>
31
+ rmLineOption(props.categories, props.series, theme.value, palette.value.cat, {
32
+ area: props.area,
33
+ step: props.step,
34
+ smooth: props.smooth,
35
+ decal: decal.value,
36
+ }),
37
+ )
38
+ </script>
39
+
40
+ <template>
41
+ <RmChart :option="option" :height="height" :description="description" />
42
+ </template>
@@ -56,5 +56,5 @@ defineEmits<{
56
56
  </template>
57
57
 
58
58
  <style scoped>
59
- .rm-nav-link{background:none;border:none;border-radius:var(--r-radius-lg);cursor:pointer;gap:8px;padding:6px 12px 6px 6px;text-align:left;width:100%}.rm-nav-link,.rm-nav-link__icon{align-items:center;display:flex;transition:all var(--r-transition-fast)}.rm-nav-link__icon{background-color:var(--r-color-gray-100);border-radius:var(--r-radius-md);color:var(--r-color-gray-500);flex-shrink:0;height:28px;justify-content:center;width:28px}.rm-nav-link__label{color:#1e293b;flex:1;font-family:var(--r-font-family);font-size:14px;font-weight:400;line-height:160%;overflow:hidden;text-overflow:ellipsis;transition:all var(--r-transition-fast);white-space:nowrap}.rm-nav-link__caret{align-items:center;color:var(--r-color-gray-400);display:flex;flex-shrink:0;transition:transform .25s ease,color var(--r-transition-fast)}.rm-nav-link__caret--expanded{transform:rotate(-180deg)}.rm-nav-link:not(.rm-nav-link--active):hover{background-color:var(--r-color-primary-50)}.rm-nav-link:not(.rm-nav-link--active):hover .rm-nav-link__icon{background-color:var(--r-color-primary-100);color:var(--r-color-primary-600)}.rm-nav-link:not(.rm-nav-link--active):hover .rm-nav-link__caret{color:var(--r-color-primary-600)}.rm-nav-link--active{background-color:var(--r-color-gray-100)}.rm-nav-link--active .rm-nav-link__icon{background-color:var(--r-color-primary-600);color:var(--r-color-white)}.rm-nav-link--active .rm-nav-link__caret{color:var(--r-color-primary-600)}
59
+ .rm-nav-link{background:none;border:none;border-radius:var(--radius-lg);cursor:pointer;gap:8px;padding:6px 12px 6px 6px;text-align:left;width:100%}.rm-nav-link,.rm-nav-link__icon{align-items:center;display:flex;transition:all var(--duration-fast) var(--ease-in-out)}.rm-nav-link__icon{background-color:var(--bg-surface-3);border-radius:var(--radius-md);color:var(--text-secondary);flex-shrink:0;height:28px;justify-content:center;width:28px}.rm-nav-link__label{color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:14px;font-weight:400;line-height:160%;overflow:hidden;text-overflow:ellipsis;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.rm-nav-link__caret{align-items:center;color:var(--text-muted);display:flex;flex-shrink:0;transition:transform .25s ease,color var(--duration-fast) var(--ease-in-out)}.rm-nav-link__caret--expanded{transform:rotate(-180deg)}.rm-nav-link:not(.rm-nav-link--active):hover{background-color:var(--bg-hover)}.rm-nav-link:not(.rm-nav-link--active):hover .rm-nav-link__icon{background-color:var(--bg-hover);color:var(--color-healthcare)}.rm-nav-link:not(.rm-nav-link--active):hover .rm-nav-link__caret{color:var(--color-healthcare)}.rm-nav-link--active{background-color:var(--bg-surface-3)}.rm-nav-link--active .rm-nav-link__icon{background-color:var(--color-healthcare);color:var(--color-text-inverse)}.rm-nav-link--active .rm-nav-link__caret{color:var(--color-healthcare)}
60
60
  </style>
@@ -33,5 +33,5 @@ defineEmits<{
33
33
  </template>
34
34
 
35
35
  <style scoped>
36
- .toolbar-pill{align-items:center;background-color:#fff;border:1px solid #dbdef0;border-radius:38px;color:var(--r-color-gray-600);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--r-text-sm);font-weight:var(--r-font-weight-medium);gap:4px;padding:4px 12px;transition:all var(--r-transition-fast);white-space:nowrap}.toolbar-pill:hover{box-shadow:0 1px 2px 0 rgba(30,41,59,.1)}.toolbar-pill--active{background-color:#2176ff;border-color:#2176ff;color:var(--r-color-white)}.toolbar-pill--active:hover{background-color:#1a65e0;border-color:#1a65e0;box-shadow:none}.toolbar-pill--primary{background-color:#2176ff;border-color:#2176ff;color:var(--r-color-white)}.toolbar-pill--primary:hover{background-color:#1a65e0;border-color:#1a65e0;box-shadow:none}.toolbar-pill--secondary{background-color:var(--r-color-gray-100);border-color:transparent;color:var(--r-color-gray-800)}.toolbar-pill--secondary:hover{background-color:var(--r-color-gray-200);border-color:transparent}.toolbar-pill--pharma{background-color:#f7520e;border-color:#f7520e;color:var(--r-color-white)}.toolbar-pill--pharma:hover{background-color:#d9470c;border-color:#d9470c}.toolbar-pill--discovery{background-color:#07816f;border-color:#07816f;color:var(--r-color-white)}.toolbar-pill--discovery:hover{background-color:#066b5c;border-color:#066b5c}.toolbar-pill--databank{background-color:#e04380;border-color:#e04380;color:var(--r-color-white)}.toolbar-pill--databank:hover{background-color:#c73a6f;border-color:#c73a6f}
36
+ .toolbar-pill{align-items:center;background-color:var(--color-text-inverse);border:1px solid var(--border-default);border-radius:38px;color:var(--text-secondary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);gap:4px;padding:4px 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.toolbar-pill:hover{box-shadow:0 1px 2px 0 rgba(var(--shadow-rgb),.1)}.toolbar-pill--active{background-color:var(--color-healthcare);border-color:var(--color-healthcare);color:var(--color-text-inverse)}.toolbar-pill--active:hover{background-color:var(--color-healthcare-active);border-color:var(--color-healthcare-active);box-shadow:none}.toolbar-pill--primary{background-color:var(--color-healthcare);border-color:var(--color-healthcare);color:var(--color-text-inverse)}.toolbar-pill--primary:hover{background-color:var(--color-healthcare-active);border-color:var(--color-healthcare-active);box-shadow:none}.toolbar-pill--secondary{background-color:var(--bg-surface-3);border-color:transparent;color:var(--text-primary)}.toolbar-pill--secondary:hover{background-color:var(--border-default);border-color:transparent}.toolbar-pill--pharma{background-color:var(--color-pharma);border-color:var(--color-pharma);color:var(--color-text-inverse)}.toolbar-pill--pharma:hover{background-color:var(--color-pharma-active);border-color:var(--color-pharma-active)}.toolbar-pill--discovery{background-color:var(--color-discovery);border-color:var(--color-discovery);color:var(--color-text-inverse)}.toolbar-pill--discovery:hover{background-color:var(--color-discovery-active);border-color:var(--color-discovery-active)}.toolbar-pill--databank{background-color:var(--color-databank);border-color:var(--color-databank);color:var(--color-text-inverse)}.toolbar-pill--databank:hover{background-color:var(--color-databank-active);border-color:var(--color-databank-active)}
37
37
  </style>
@@ -30,5 +30,5 @@ defineEmits<{
30
30
  </template>
31
31
 
32
32
  <style scoped>
33
- .rm-pill-tabs{display:flex;gap:4px}.rm-pill-tabs__tab{background:transparent;border:none;border-radius:var(--r-radius-lg);color:var(--r-color-gray-500);cursor:pointer;font-family:var(--r-font-family);font-size:14px;font-weight:400;padding:6px 14px;transition:all var(--r-transition-fast)}.rm-pill-tabs__tab:hover{color:var(--r-color-gray-700)}.rm-pill-tabs__tab--active{background-color:var(--r-color-gray-100);color:var(--r-color-gray-900);font-weight:600}
33
+ .rm-pill-tabs{display:flex;gap:4px}.rm-pill-tabs__tab{background:transparent;border:none;border-radius:var(--radius-lg);cursor:pointer;font-family:var(--font-sans);font-size:14px;font-weight:400;padding:6px 14px;transition:all var(--duration-fast) var(--ease-in-out)}.rm-pill-tabs__tab,.rm-pill-tabs__tab:hover{color:var(--text-secondary)}.rm-pill-tabs__tab--active{background-color:var(--bg-surface-3);color:var(--text-primary);font-weight:600}
34
34
  </style>
@@ -1,38 +1,122 @@
1
1
  <script setup lang="ts">
2
- import { PhMagnifyingGlass } from '@phosphor-icons/vue'
2
+ import { PhMagnifyingGlass, PhX } from '@phosphor-icons/vue'
3
+
4
+ export interface SearchSuggestion {
5
+ label: string
6
+ field?: string
7
+ }
3
8
 
4
9
  const model = defineModel<string>({ default: '' })
5
10
 
6
- withDefaults(defineProps<{
11
+ const props = withDefaults(defineProps<{
7
12
  advancedActive?: boolean
13
+ advancedCount?: number
14
+ suggestions?: SearchSuggestion[]
15
+ placeholder?: string
8
16
  }>(), {
9
17
  advancedActive: false,
18
+ advancedCount: 0,
19
+ suggestions: () => [],
20
+ placeholder: 'Search...',
10
21
  })
11
22
 
12
- defineEmits<{
23
+ const emit = defineEmits<{
13
24
  advancedSearch: []
25
+ search: [value: string]
26
+ typing: [value: string]
27
+ selectSuggestion: [suggestion: SearchSuggestion]
14
28
  }>()
29
+
30
+ const showSuggestions = ref(false)
31
+ const barRef = ref<HTMLElement>()
32
+ let debounceTimer: ReturnType<typeof setTimeout>
33
+
34
+ function onInput() {
35
+ clearTimeout(debounceTimer)
36
+ debounceTimer = setTimeout(() => {
37
+ emit('typing', model.value)
38
+ if (model.value.length >= 2) {
39
+ showSuggestions.value = true
40
+ } else {
41
+ showSuggestions.value = false
42
+ }
43
+ }, 300)
44
+ }
45
+
46
+ function submitSearch() {
47
+ showSuggestions.value = false
48
+ emit('search', model.value)
49
+ }
50
+
51
+ function selectSuggestion(sugg: SearchSuggestion) {
52
+ model.value = sugg.label
53
+ showSuggestions.value = false
54
+ emit('selectSuggestion', sugg)
55
+ emit('search', sugg.label)
56
+ }
57
+
58
+ function clearSearch() {
59
+ model.value = ''
60
+ showSuggestions.value = false
61
+ emit('search', '')
62
+ }
63
+
64
+ function onClickOutside(e: Event) {
65
+ if (barRef.value && !barRef.value.contains(e.target as Node)) {
66
+ showSuggestions.value = false
67
+ }
68
+ }
69
+
70
+ onMounted(() => document.addEventListener('click', onClickOutside))
71
+ onUnmounted(() => document.removeEventListener('click', onClickOutside))
15
72
  </script>
16
73
 
17
74
  <template>
18
- <div class="search-bar" :class="{ 'search-bar--advanced-active': advancedActive }">
19
- <PhMagnifyingGlass :size="16" class="search-bar__icon" />
20
- <input
21
- v-model="model"
22
- type="text"
23
- placeholder="Search..."
24
- class="search-bar__input"
25
- >
26
- <button
27
- class="search-bar__advanced"
28
- :class="{ 'search-bar__advanced--active': advancedActive }"
29
- @click="$emit('advancedSearch')"
30
- >
31
- Advanced search
32
- </button>
75
+ <div ref="barRef" class="search-bar-wrapper">
76
+ <div class="search-bar" :class="{ 'search-bar--advanced-active': advancedActive, 'search-bar--has-value': model.length > 0 }">
77
+ <PhMagnifyingGlass :size="16" class="search-bar__icon" />
78
+ <input
79
+ v-model="model"
80
+ type="text"
81
+ :placeholder="placeholder"
82
+ class="search-bar__input"
83
+ @input="onInput"
84
+ @keydown.enter="submitSearch"
85
+ >
86
+ <button
87
+ v-if="model.length > 0"
88
+ class="search-bar__clear"
89
+ @click="clearSearch"
90
+ >
91
+ <PhX :size="12" />
92
+ </button>
93
+ <button
94
+ class="search-bar__advanced"
95
+ :class="{ 'search-bar__advanced--active': advancedActive }"
96
+ @click="$emit('advancedSearch')"
97
+ >
98
+ Advanced search
99
+ <span v-if="advancedCount > 0" class="search-bar__advanced-badge">{{ advancedCount }}</span>
100
+ </button>
101
+ </div>
102
+
103
+ <Transition name="suggestions">
104
+ <div v-if="showSuggestions && suggestions.length > 0" class="search-bar__suggestions">
105
+ <button
106
+ v-for="(sugg, i) in suggestions"
107
+ :key="i"
108
+ class="search-bar__suggestion"
109
+ @click="selectSuggestion(sugg)"
110
+ >
111
+ <PhMagnifyingGlass :size="14" class="search-bar__suggestion-icon" />
112
+ <span class="search-bar__suggestion-label">{{ sugg.label }}</span>
113
+ <span v-if="sugg.field" class="search-bar__suggestion-field">{{ sugg.field.split('.').pop() }}</span>
114
+ </button>
115
+ </div>
116
+ </Transition>
33
117
  </div>
34
118
  </template>
35
119
 
36
120
  <style scoped>
37
- .search-bar{align-items:center;border:1px solid var(--r-color-gray-200);border-radius:var(--r-radius-full);display:inline-flex;gap:var(--r-space-2);height:32px;min-width:200px;padding:var(--r-space-1) var(--r-space-3);transition:border-color var(--r-transition-fast)}.search-bar:focus-within{border-color:var(--r-color-primary-400)}.search-bar__icon{color:var(--r-color-gray-400);flex-shrink:0}.search-bar__input{background:transparent;border:none;color:var(--r-color-gray-700);font-family:inherit;font-size:var(--r-text-sm);outline:none;width:100%}.search-bar__input::-moz-placeholder{color:var(--r-color-gray-400)}.search-bar__input::placeholder{color:var(--r-color-gray-400)}.search-bar__advanced{background:none;border:none;border-left:1px solid var(--r-color-gray-200);color:var(--r-color-gray-500);cursor:pointer;flex-shrink:0;font-family:inherit;font-size:var(--r-text-sm);padding:0 0 0 var(--r-space-2);text-decoration:underline;transition:color var(--r-transition-fast);white-space:nowrap}.search-bar__advanced:hover{color:var(--r-color-primary-600)}.search-bar--advanced-active{border-color:#4164ee}.search-bar__advanced--active{color:#4164ee;font-weight:var(--r-font-weight-medium)}
121
+ .search-bar-wrapper{position:relative}.search-bar{align-items:center;border:1px solid var(--border-default);border-radius:var(--radius-pill);display:inline-flex;gap:var(--space-8);height:32px;min-width:200px;padding:var(--space-4) var(--space-12);transition:border-color var(--duration-fast) var(--ease-in-out)}.search-bar:focus-within{border-color:var(--color-secondary);box-shadow:var(--shadow-focus)}.search-bar__icon{color:var(--text-muted);flex-shrink:0}.search-bar__input{background:transparent;border:none;color:var(--text-secondary);font-family:var(--font-sans);font-size:var(--font-14);outline:none;width:100%}.search-bar__input::-moz-placeholder{color:var(--text-muted)}.search-bar__input::placeholder{color:var(--text-muted)}.search-bar__clear{align-items:center;border:none;border-radius:50%;cursor:pointer;display:flex;flex-shrink:0;height:18px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:18px}.search-bar__clear,.search-bar__clear:hover{background:var(--border-default);color:var(--text-secondary)}.search-bar__advanced{background:none;border:none;border-left:1px solid var(--border-default);color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-family:var(--font-sans);font-size:var(--font-14);padding:0 0 0 var(--space-8);text-decoration:underline;transition:color var(--duration-fast) var(--ease-in-out);white-space:nowrap}.search-bar__advanced:hover{color:var(--color-healthcare)}.search-bar--advanced-active{border-color:var(--color-healthcare)}.search-bar__advanced--active{color:var(--color-healthcare);font-weight:500}.search-bar__advanced-badge{align-items:center;background-color:var(--color-healthcare);border-radius:100px;color:var(--color-text-inverse);display:inline-flex;font-size:10px;font-weight:600;height:16px;justify-content:center;line-height:1;min-width:16px;padding:0 4px}.search-bar__suggestions{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08);left:0;max-height:280px;overflow-y:auto;padding:4px;position:absolute;right:0;top:calc(100% + 4px);z-index:50}.search-bar__suggestion{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:8px;padding:8px 10px;text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.search-bar__suggestion:hover{background-color:var(--bg-surface-2)}.search-bar__suggestion-icon{color:var(--text-muted);flex-shrink:0}.search-bar__suggestion-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-bar__suggestion-field{background:var(--bg-surface-2);border-radius:3px;color:var(--text-muted);flex-shrink:0;font-size:11px;padding:1px 6px}.suggestions-enter-active,.suggestions-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.suggestions-enter-from,.suggestions-leave-to{opacity:0;transform:translateY(-4px)}
38
122
  </style>
@@ -139,5 +139,5 @@ function handleClick(item: RmSidebarMenuItem) {
139
139
  </template>
140
140
 
141
141
  <style scoped>
142
- .rm-sidebar{background-color:var(--r-color-white);border-right:1px solid var(--r-color-gray-200);bottom:0;display:flex;flex-direction:column;left:0;overflow-y:auto;position:fixed;top:0;transition:width var(--r-transition-base);width:240px;z-index:50}.rm-sidebar--collapsed{width:64px}.rm-sidebar__header{align-items:center;border-bottom:1px solid var(--r-color-gray-200);display:flex;justify-content:space-between;min-height:64px;padding:0 16px}.rm-sidebar__brand{align-items:center;display:flex;gap:8px}.rm-sidebar__logo{color:var(--r-color-primary-600)}.rm-sidebar__name{color:var(--r-color-gray-900);font-family:var(--r-font-family);font-size:16px;font-weight:700;white-space:nowrap}.rm-sidebar__toggle{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--r-color-gray-500);cursor:pointer;display:flex;justify-content:center;padding:4px}.rm-sidebar__toggle:hover{background-color:var(--r-color-gray-100)}.rm-sidebar__nav{display:flex;flex:1;flex-direction:column;gap:6px;padding:8px}.rm-sidebar__submenu{display:flex;flex-direction:column;gap:6px;padding-left:20px}.rm-sidebar__submenu-item{animation:rm-submenu-fade-in .25s ease both}@keyframes rm-submenu-fade-in{0%{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:translateX(0)}}.rm-sidebar__footer{border-top:1px solid var(--r-color-gray-200);padding:12px}
142
+ .rm-sidebar{background-color:var(--color-text-inverse);border-right:1px solid var(--border-default);bottom:0;display:flex;flex-direction:column;left:0;overflow-y:auto;position:fixed;top:0;transition:width var(--duration-base) var(--ease-in-out);width:240px;z-index:50}.rm-sidebar--collapsed{width:64px}.rm-sidebar__header{align-items:center;border-bottom:1px solid var(--border-default);display:flex;justify-content:space-between;min-height:64px;padding:0 16px}.rm-sidebar__brand{align-items:center;display:flex;gap:8px}.rm-sidebar__logo{color:var(--color-healthcare)}.rm-sidebar__name{color:var(--text-primary);font-family:var(--font-sans);font-size:16px;font-weight:700;white-space:nowrap}.rm-sidebar__toggle{align-items:center;background:none;border:none;border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;display:flex;justify-content:center;padding:4px}.rm-sidebar__toggle:hover{background-color:var(--bg-surface-3)}.rm-sidebar__nav{display:flex;flex:1;flex-direction:column;gap:6px;padding:8px}.rm-sidebar__submenu{display:flex;flex-direction:column;gap:6px;padding-left:20px}.rm-sidebar__submenu-item{animation:rm-submenu-fade-in .25s ease both}@keyframes rm-submenu-fade-in{0%{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:translateX(0)}}.rm-sidebar__footer{border-top:1px solid var(--border-default);padding:12px}
143
143
  </style>
@@ -0,0 +1,33 @@
1
+ <!--
2
+ RmSparklineChart - micro-courbe pour une cellule de tableau ou une tuile KPI.
3
+ Sans axes ni tooltip : elle donne une forme, pas une valeur. La valeur doit
4
+ rester lisible en texte a cote, le survol n'etant jamais le seul acces a
5
+ l'information.
6
+ -->
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue'
9
+ import { useChartTheme } from '../composables/useChartTheme'
10
+ import { rmSparklineOption, RM_CHART_HEIGHTS } from '../utils/chart'
11
+
12
+ const props = withDefaults(defineProps<{
13
+ values: (number | null)[]
14
+ /** Couleur explicite ; par defaut la primaire de la palette active. */
15
+ color?: string
16
+ height?: keyof typeof RM_CHART_HEIGHTS | number
17
+ description?: string
18
+ }>(), {
19
+ color: undefined,
20
+ height: 'sparkline',
21
+ description: undefined,
22
+ })
23
+
24
+ // Pas de `theme` ici : une sparkline n'a ni axe ni tooltip a colorer. Le
25
+ // basculement jour/nuit la recalcule quand meme, parce que `palette` en depend.
26
+ const { palette } = useChartTheme()
27
+
28
+ const option = computed(() => rmSparklineOption(props.values, props.color ?? palette.value.primary))
29
+ </script>
30
+
31
+ <template>
32
+ <RmChart :option="option" :height="height" :description="description" />
33
+ </template>
@@ -13,13 +13,13 @@ const props = withDefaults(defineProps<RmTabsProps>(), {
13
13
  variant: 'default',
14
14
  })
15
15
 
16
- const defaultColors = { color: '#4164EE', bg: 'rgba(65, 100, 238, 0.05)' }
16
+ const defaultColors = { color: 'var(--color-healthcare)', bg: 'rgba(var(--healthcare-rgb), 0.05)' }
17
17
 
18
18
  const variantColors: Record<string, { color: string; bg: string }> = {
19
19
  default: defaultColors,
20
- pharma: { color: '#F7520E', bg: 'rgba(247, 82, 14, 0.05)' },
21
- discovery: { color: '#07816F', bg: 'rgba(7, 129, 111, 0.05)' },
22
- databank: { color: '#E04380', bg: 'rgba(224, 67, 128, 0.05)' },
20
+ pharma: { color: 'var(--color-pharma)', bg: 'rgba(var(--pharma-rgb), 0.05)' },
21
+ discovery: { color: 'var(--color-discovery)', bg: 'rgba(var(--discovery-rgb), 0.05)' },
22
+ databank: { color: 'var(--color-databank)', bg: 'rgba(var(--databank-rgb), 0.05)' },
23
23
  }
24
24
 
25
25
  const currentColors = computed((): { color: string; bg: string } => variantColors[props.variant] ?? defaultColors)
@@ -101,5 +101,5 @@ watch(() => props.modelValue, (val) => updateIndicator(val))
101
101
  </template>
102
102
 
103
103
  <style scoped>
104
- .rm-tabs{background-color:var(--r-color-white);border-bottom:1px solid var(--r-color-gray-200)}.rm-tabs__list{display:flex;padding:8px 20px 0;position:relative}.rm-tabs__tab{background:none;border:none;border-radius:4px 4px 0 0;color:var(--r-color-gray-500);cursor:pointer;font-family:var(--r-font-family);font-size:14px;font-weight:400;padding:12px 16px;position:relative;transition:color var(--r-transition-fast),background var(--r-transition-fast);white-space:nowrap}.rm-tabs__tab:hover{background:var(--tab-active-bg);color:var(--r-color-gray-700)}.rm-tabs__tab--active{font-weight:500}.rm-tabs__indicator{border-radius:2px 2px 0 0;bottom:0;height:2px;left:0;position:absolute;transition:transform .3s cubic-bezier(.4,0,.2,1),width .3s cubic-bezier(.4,0,.2,1)}
104
+ .rm-tabs{background-color:var(--color-text-inverse);border-bottom:1px solid var(--border-default)}.rm-tabs__list{display:flex;padding:8px 20px 0;position:relative}.rm-tabs__tab{background:none;border:none;border-radius:4px 4px 0 0;color:var(--text-secondary);cursor:pointer;font-family:var(--font-sans);font-size:14px;font-weight:400;padding:12px 16px;position:relative;transition:color var(--duration-fast) var(--ease-in-out),background var(--duration-fast) var(--ease-in-out);white-space:nowrap}.rm-tabs__tab:hover{background:var(--tab-active-bg);color:var(--text-secondary)}.rm-tabs__tab--active{font-weight:500}.rm-tabs__indicator{border-radius:2px 2px 0 0;bottom:0;height:2px;left:0;position:absolute;transition:transform .3s cubic-bezier(.4,0,.2,1),width .3s cubic-bezier(.4,0,.2,1)}
105
105
  </style>
@@ -130,5 +130,5 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
130
130
  </template>
131
131
 
132
132
  <style scoped>
133
- .toolbar-filter{position:relative}.toolbar-filter__caret{opacity:.6;transition:transform var(--r-transition-fast)}.toolbar-filter__caret--open{transform:rotate(180deg)}.toolbar-filter__dropdown{background-color:var(--r-color-white);border:1px solid var(--r-color-gray-200);border-radius:var(--r-radius-lg);box-shadow:0 4px 12px rgba(0,0,0,.08),0 2px 4px rgba(0,0,0,.04);left:0;max-height:260px;min-width:200px;overflow-y:auto;padding:var(--r-space-1);position:absolute;top:calc(100% + var(--r-space-2));z-index:50}.toolbar-filter__option{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--r-color-gray-700);cursor:pointer;display:flex;font-family:inherit;font-size:var(--r-text-sm);gap:var(--r-space-2);padding:var(--r-space-2) var(--r-space-3);text-align:left;transition:background-color var(--r-transition-fast);width:100%}.toolbar-filter__option:hover{background-color:var(--r-color-gray-50)}.toolbar-filter__option--selected{color:var(--r-color-gray-900);font-weight:var(--r-font-weight-medium)}.toolbar-filter__checkbox{align-items:center;border:1.5px solid var(--r-color-gray-300);border-radius:var(--r-radius-sm);display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--r-transition-fast);width:16px}.toolbar-filter__checkbox--checked{background-color:var(--r-color-primary-600);border-color:var(--r-color-primary-600);color:var(--r-color-white)}.toolbar-filter__radio{align-items:center;border:1.5px solid var(--r-color-gray-300);border-radius:var(--r-radius-full);display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--r-transition-fast);width:16px}.toolbar-filter__radio--checked{border-color:var(--r-color-primary-600)}.toolbar-filter__radio--checked:after{background-color:var(--r-color-primary-600);border-radius:var(--r-radius-full);content:"";height:8px;width:8px}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--r-transition-fast),transform var(--r-transition-fast)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
133
+ .toolbar-filter{position:relative}.toolbar-filter__caret{opacity:.6;transition:transform var(--duration-fast) var(--ease-in-out)}.toolbar-filter__caret--open{transform:rotate(180deg)}.toolbar-filter__dropdown{background-color:var(--color-text-inverse);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08),0 2px 4px rgba(var(--shadow-rgb),.04);left:0;max-height:260px;min-width:200px;overflow-y:auto;padding:var(--space-4);position:absolute;top:calc(100% + var(--space-8));z-index:50}.toolbar-filter__option{align-items:center;background:none;border:none;border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14);gap:var(--space-8);padding:var(--space-8) var(--space-12);text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.toolbar-filter__option:hover{background-color:var(--bg-surface-2)}.toolbar-filter__option--selected{color:var(--text-primary);font-weight:var(--weight-medium)}.toolbar-filter__checkbox{align-items:center;border:1.5px solid var(--border-default);border-radius:var(--radius-sm);display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.toolbar-filter__checkbox--checked{background-color:var(--color-healthcare);border-color:var(--color-healthcare);color:var(--color-text-inverse)}.toolbar-filter__radio{align-items:center;border:1.5px solid var(--border-default);border-radius:var(--radius-pill);display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.toolbar-filter__radio--checked{border-color:var(--color-healthcare)}.toolbar-filter__radio--checked:after{background-color:var(--color-healthcare);border-radius:var(--radius-pill);content:"";height:8px;width:8px}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
134
134
  </style>
@@ -0,0 +1,7 @@
1
+ export declare function useChartTheme(): {
2
+ night: import("vue").Ref<boolean, boolean>;
3
+ cvd: import("vue").Ref<boolean, boolean>;
4
+ theme: import("vue").ComputedRef<import("../utils/chart").RmChartTheme>;
5
+ palette: import("vue").ComputedRef<import("../utils/chart").RmPalette>;
6
+ decal: import("vue").ComputedRef<boolean>;
7
+ };
@@ -0,0 +1,27 @@
1
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
2
+ import { rmChartTheme, rmPalette } from "../utils/chart.js";
3
+ const CVD_CLASSES = ["cvd-deutera", "cvd-protan", "cvd-trita", "cvd-mono"];
4
+ export function useChartTheme() {
5
+ const night = ref(false);
6
+ const cvd = ref(false);
7
+ let observer = null;
8
+ function read() {
9
+ if (typeof document === "undefined") return;
10
+ const list = document.body.classList;
11
+ night.value = list.contains("night");
12
+ cvd.value = CVD_CLASSES.some((c) => list.contains(c));
13
+ }
14
+ onMounted(() => {
15
+ read();
16
+ observer = new MutationObserver(read);
17
+ observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
18
+ });
19
+ onBeforeUnmount(() => {
20
+ observer?.disconnect();
21
+ observer = null;
22
+ });
23
+ const theme = computed(() => rmChartTheme(night.value));
24
+ const palette = computed(() => rmPalette(cvd.value ? "cvdSafe" : "default", night.value));
25
+ const decal = computed(() => cvd.value);
26
+ return { night, cvd, theme, palette, decal };
27
+ }
@@ -0,0 +1,17 @@
1
+ export type RmCvdMode = 'none' | 'deutera' | 'protan' | 'trita' | 'mono';
2
+ export type RmTextScale = 'normal' | 'large' | 'xl';
3
+ export declare function useRomulusTheme(): {
4
+ night: Readonly<import("vue").Ref<boolean, boolean>>;
5
+ highContrast: Readonly<import("vue").Ref<boolean, boolean>>;
6
+ reduceMotion: Readonly<import("vue").Ref<boolean, boolean>>;
7
+ compact: Readonly<import("vue").Ref<boolean, boolean>>;
8
+ cvd: Readonly<import("vue").Ref<RmCvdMode, RmCvdMode>>;
9
+ textScale: Readonly<import("vue").Ref<RmTextScale, RmTextScale>>;
10
+ setNight: (value: boolean) => void;
11
+ setHighContrast: (value: boolean) => void;
12
+ setReduceMotion: (value: boolean) => void;
13
+ setCompact: (value: boolean) => void;
14
+ setCvd: (mode: RmCvdMode) => void;
15
+ setTextScale: (scale: RmTextScale) => void;
16
+ reset: () => void;
17
+ };
@@ -0,0 +1,98 @@
1
+ import { onMounted, readonly, ref } from "vue";
2
+ const CVD_CLASSES = {
3
+ deutera: "cvd-deutera",
4
+ protan: "cvd-protan",
5
+ trita: "cvd-trita",
6
+ mono: "cvd-mono"
7
+ };
8
+ const TEXT_CLASSES = {
9
+ large: "text-large",
10
+ xl: "text-xl"
11
+ };
12
+ export function useRomulusTheme() {
13
+ const night = ref(false);
14
+ const highContrast = ref(false);
15
+ const reduceMotion = ref(false);
16
+ const compact = ref(false);
17
+ const cvd = ref("none");
18
+ const textScale = ref("normal");
19
+ function apply(mutate) {
20
+ if (typeof document === "undefined") return;
21
+ const { body } = document;
22
+ body.classList.add("theme-switching");
23
+ mutate();
24
+ requestAnimationFrame(() => {
25
+ requestAnimationFrame(() => body.classList.remove("theme-switching"));
26
+ });
27
+ }
28
+ const setNight = (value) => apply(() => {
29
+ night.value = value;
30
+ document.body.classList.toggle("night", value);
31
+ });
32
+ const setHighContrast = (value) => apply(() => {
33
+ highContrast.value = value;
34
+ document.body.classList.toggle("high-contrast", value);
35
+ });
36
+ const setReduceMotion = (value) => apply(() => {
37
+ reduceMotion.value = value;
38
+ document.body.classList.toggle("reduce-motion", value);
39
+ });
40
+ const setCompact = (value) => apply(() => {
41
+ compact.value = value;
42
+ document.body.classList.toggle("density-compact", value);
43
+ });
44
+ const setCvd = (mode) => apply(() => {
45
+ cvd.value = mode;
46
+ const { body } = document;
47
+ Object.values(CVD_CLASSES).forEach((c) => body.classList.remove(c));
48
+ if (mode !== "none") body.classList.add(CVD_CLASSES[mode]);
49
+ });
50
+ const setTextScale = (scale) => apply(() => {
51
+ textScale.value = scale;
52
+ const { body } = document;
53
+ Object.values(TEXT_CLASSES).forEach((c) => body.classList.remove(c));
54
+ if (scale !== "normal") body.classList.add(TEXT_CLASSES[scale]);
55
+ });
56
+ const reset = () => apply(() => {
57
+ const { body } = document;
58
+ night.value = false;
59
+ highContrast.value = false;
60
+ reduceMotion.value = false;
61
+ compact.value = false;
62
+ cvd.value = "none";
63
+ textScale.value = "normal";
64
+ body.classList.remove(
65
+ "night",
66
+ "high-contrast",
67
+ "reduce-motion",
68
+ "density-compact",
69
+ ...Object.values(CVD_CLASSES),
70
+ ...Object.values(TEXT_CLASSES)
71
+ );
72
+ });
73
+ onMounted(() => {
74
+ const list = document.body.classList;
75
+ night.value = list.contains("night");
76
+ highContrast.value = list.contains("high-contrast");
77
+ reduceMotion.value = list.contains("reduce-motion");
78
+ compact.value = list.contains("density-compact");
79
+ const found = Object.entries(CVD_CLASSES).find(([, c]) => list.contains(c));
80
+ cvd.value = found ? found[0] : "none";
81
+ textScale.value = list.contains("text-xl") ? "xl" : list.contains("text-large") ? "large" : "normal";
82
+ });
83
+ return {
84
+ night: readonly(night),
85
+ highContrast: readonly(highContrast),
86
+ reduceMotion: readonly(reduceMotion),
87
+ compact: readonly(compact),
88
+ cvd: readonly(cvd),
89
+ textScale: readonly(textScale),
90
+ setNight,
91
+ setHighContrast,
92
+ setReduceMotion,
93
+ setCompact,
94
+ setCvd,
95
+ setTextScale,
96
+ reset
97
+ };
98
+ }