qdadm 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +270 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/package.json +48 -0
  5. package/src/assets/logo.svg +6 -0
  6. package/src/components/BoolCell.vue +28 -0
  7. package/src/components/dialogs/BulkStatusDialog.vue +43 -0
  8. package/src/components/dialogs/MultiStepDialog.vue +321 -0
  9. package/src/components/dialogs/SimpleDialog.vue +108 -0
  10. package/src/components/dialogs/UnsavedChangesDialog.vue +87 -0
  11. package/src/components/display/CardsGrid.vue +155 -0
  12. package/src/components/display/CopyableId.vue +92 -0
  13. package/src/components/display/EmptyState.vue +114 -0
  14. package/src/components/display/IntensityBar.vue +171 -0
  15. package/src/components/display/RichCardsGrid.vue +220 -0
  16. package/src/components/editors/JsonEditorFoldable.vue +467 -0
  17. package/src/components/editors/JsonStructuredField.vue +218 -0
  18. package/src/components/editors/JsonViewer.vue +91 -0
  19. package/src/components/editors/KeyValueEditor.vue +314 -0
  20. package/src/components/editors/LanguageEditor.vue +245 -0
  21. package/src/components/editors/ScopeEditor.vue +341 -0
  22. package/src/components/editors/VanillaJsonEditor.vue +185 -0
  23. package/src/components/forms/FormActions.vue +104 -0
  24. package/src/components/forms/FormField.vue +64 -0
  25. package/src/components/forms/FormTab.vue +217 -0
  26. package/src/components/forms/FormTabs.vue +108 -0
  27. package/src/components/index.js +44 -0
  28. package/src/components/layout/AppLayout.vue +430 -0
  29. package/src/components/layout/Breadcrumb.vue +106 -0
  30. package/src/components/layout/PageHeader.vue +75 -0
  31. package/src/components/layout/PageLayout.vue +93 -0
  32. package/src/components/lists/ActionButtons.vue +41 -0
  33. package/src/components/lists/ActionColumn.vue +37 -0
  34. package/src/components/lists/FilterBar.vue +53 -0
  35. package/src/components/lists/ListPage.vue +319 -0
  36. package/src/composables/index.js +19 -0
  37. package/src/composables/useApp.js +43 -0
  38. package/src/composables/useAuth.js +49 -0
  39. package/src/composables/useBareForm.js +143 -0
  40. package/src/composables/useBreadcrumb.js +221 -0
  41. package/src/composables/useDirtyState.js +103 -0
  42. package/src/composables/useEntityTitle.js +121 -0
  43. package/src/composables/useForm.js +254 -0
  44. package/src/composables/useGuardStore.js +37 -0
  45. package/src/composables/useJsonSyntax.js +101 -0
  46. package/src/composables/useListPageBuilder.js +1176 -0
  47. package/src/composables/useNavigation.js +89 -0
  48. package/src/composables/usePageBuilder.js +334 -0
  49. package/src/composables/useStatus.js +146 -0
  50. package/src/composables/useSubEditor.js +165 -0
  51. package/src/composables/useTabSync.js +110 -0
  52. package/src/composables/useUnsavedChangesGuard.js +122 -0
  53. package/src/entity/EntityManager.js +540 -0
  54. package/src/entity/index.js +11 -0
  55. package/src/entity/storage/ApiStorage.js +146 -0
  56. package/src/entity/storage/LocalStorage.js +220 -0
  57. package/src/entity/storage/MemoryStorage.js +201 -0
  58. package/src/entity/storage/index.js +10 -0
  59. package/src/index.js +29 -0
  60. package/src/kernel/Kernel.js +234 -0
  61. package/src/kernel/index.js +7 -0
  62. package/src/module/index.js +16 -0
  63. package/src/module/moduleRegistry.js +222 -0
  64. package/src/orchestrator/Orchestrator.js +141 -0
  65. package/src/orchestrator/index.js +8 -0
  66. package/src/orchestrator/useOrchestrator.js +61 -0
  67. package/src/plugin.js +142 -0
  68. package/src/styles/_alerts.css +48 -0
  69. package/src/styles/_code.css +33 -0
  70. package/src/styles/_dialogs.css +17 -0
  71. package/src/styles/_markdown.css +82 -0
  72. package/src/styles/_show-pages.css +84 -0
  73. package/src/styles/index.css +16 -0
  74. package/src/styles/main.css +845 -0
  75. package/src/styles/theme/components.css +286 -0
  76. package/src/styles/theme/index.css +10 -0
  77. package/src/styles/theme/tokens.css +125 -0
  78. package/src/styles/theme/utilities.css +172 -0
  79. package/src/utils/debugInjector.js +261 -0
  80. package/src/utils/formatters.js +165 -0
  81. package/src/utils/index.js +35 -0
  82. package/src/utils/transformers.js +105 -0
@@ -0,0 +1,91 @@
1
+ <script setup>
2
+ /**
3
+ * JsonViewer - Reusable JSON viewer component
4
+ *
5
+ * Simple formatted JSON display with syntax highlighting.
6
+ *
7
+ * Usage:
8
+ * <JsonViewer :model-value="jsonData" height="300px" />
9
+ */
10
+
11
+ import { computed } from 'vue'
12
+ import { highlightJson } from '../../composables/useJsonSyntax'
13
+
14
+ const props = defineProps({
15
+ modelValue: {
16
+ type: [Object, Array, String, null],
17
+ default: () => ({})
18
+ },
19
+ height: {
20
+ type: String,
21
+ default: '300px'
22
+ }
23
+ })
24
+
25
+ // Parse value if string
26
+ const parsedValue = computed(() => {
27
+ if (typeof props.modelValue === 'string') {
28
+ try {
29
+ return JSON.parse(props.modelValue)
30
+ } catch {
31
+ return props.modelValue
32
+ }
33
+ }
34
+ return props.modelValue
35
+ })
36
+
37
+ // Format JSON with syntax highlighting
38
+ const formattedJson = computed(() => {
39
+ if (parsedValue.value === null || parsedValue.value === undefined) {
40
+ return '<span class="json-null">null</span>'
41
+ }
42
+ return highlightJson(JSON.stringify(parsedValue.value, null, 2))
43
+ })
44
+ </script>
45
+
46
+ <template>
47
+ <div class="json-viewer" :style="{ maxHeight: height }">
48
+ <pre class="json-content" v-html="formattedJson"></pre>
49
+ </div>
50
+ </template>
51
+
52
+ <style scoped>
53
+ .json-viewer {
54
+ background: var(--p-surface-50);
55
+ border: 1px solid var(--p-surface-200);
56
+ border-radius: 0.375rem;
57
+ overflow: auto;
58
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
59
+ font-size: 0.8125rem;
60
+ line-height: 1.5;
61
+ }
62
+
63
+ .json-content {
64
+ padding: 1rem;
65
+ margin: 0;
66
+ white-space: pre-wrap;
67
+ word-break: break-word;
68
+ }
69
+
70
+ :deep(.json-key) {
71
+ color: #881391;
72
+ }
73
+
74
+ :deep(.json-string) {
75
+ color: #1a1aa6;
76
+ }
77
+
78
+ :deep(.json-number) {
79
+ color: #1c00cf;
80
+ }
81
+
82
+ :deep(.json-boolean) {
83
+ color: #0d22aa;
84
+ font-weight: 500;
85
+ }
86
+
87
+ :deep(.json-null) {
88
+ color: #808080;
89
+ font-style: italic;
90
+ }
91
+ </style>
@@ -0,0 +1,314 @@
1
+ <script setup>
2
+ /**
3
+ * KeyValueEditor - Reusable component for editing key-value pairs
4
+ *
5
+ * Props:
6
+ * - modelValue: Array of {key, value} objects
7
+ * - label: Field label
8
+ * - help: Help text
9
+ * - keyPlaceholder: Placeholder for key input
10
+ * - valuePlaceholder: Placeholder for value input (text mode only)
11
+ * - keySuggestions: Array of strings for autocomplete on key input
12
+ * - valueType: 'number' (default) or 'text'
13
+ * - min: Minimum value (default: 0, number mode only)
14
+ * - max: Maximum value (default: 1, number mode only)
15
+ * - step: Slider step (default: 0.1, number mode only)
16
+ * - showSign: Show +/- sign for values (default: false, number mode only)
17
+ * - colorize: Color negative/positive values (default: false, number mode only)
18
+ */
19
+
20
+ import { ref, computed } from 'vue'
21
+ import Button from 'primevue/button'
22
+ import InputText from 'primevue/inputtext'
23
+ import AutoComplete from 'primevue/autocomplete'
24
+ import Slider from 'primevue/slider'
25
+
26
+ const props = defineProps({
27
+ modelValue: {
28
+ type: Array,
29
+ default: () => []
30
+ },
31
+ label: {
32
+ type: String,
33
+ default: 'Key-Value Pairs'
34
+ },
35
+ help: {
36
+ type: String,
37
+ default: ''
38
+ },
39
+ keyPlaceholder: {
40
+ type: String,
41
+ default: 'Key'
42
+ },
43
+ valuePlaceholder: {
44
+ type: String,
45
+ default: 'Value'
46
+ },
47
+ keySuggestions: {
48
+ type: Array,
49
+ default: () => []
50
+ },
51
+ valueType: {
52
+ type: String,
53
+ default: 'number',
54
+ validator: (v) => ['number', 'text'].includes(v)
55
+ },
56
+ min: {
57
+ type: Number,
58
+ default: 0
59
+ },
60
+ max: {
61
+ type: Number,
62
+ default: 1
63
+ },
64
+ step: {
65
+ type: Number,
66
+ default: 0.1
67
+ },
68
+ showSign: {
69
+ type: Boolean,
70
+ default: false
71
+ },
72
+ colorize: {
73
+ type: Boolean,
74
+ default: false
75
+ }
76
+ })
77
+
78
+ const emit = defineEmits(['update:modelValue'])
79
+
80
+ const items = computed({
81
+ get: () => props.modelValue || [],
82
+ set: (value) => emit('update:modelValue', value)
83
+ })
84
+
85
+ const isTextMode = computed(() => props.valueType === 'text')
86
+ const hasKeySuggestions = computed(() => props.keySuggestions.length > 0)
87
+ const filteredKeySuggestions = ref([])
88
+
89
+ const newItem = ref({
90
+ key: '',
91
+ value: props.valueType === 'text' ? '' : (props.min + props.max) / 2
92
+ })
93
+
94
+ // Filter key suggestions for autocomplete (exclude already used keys)
95
+ function searchKeys(event) {
96
+ const query = event.query.toLowerCase()
97
+ const usedKeys = items.value.map(item => item.key)
98
+ filteredKeySuggestions.value = props.keySuggestions
99
+ .filter(k => !usedKeys.includes(k))
100
+ .filter(k => k.toLowerCase().includes(query))
101
+ }
102
+
103
+ function formatValue(value) {
104
+ if (isTextMode.value) {
105
+ return value
106
+ }
107
+ const formatted = Number(value).toFixed(2)
108
+ if (props.showSign && value > 0) {
109
+ return '+' + formatted
110
+ }
111
+ return formatted
112
+ }
113
+
114
+ function addItem() {
115
+ if (!newItem.value.key) return
116
+ if (isTextMode.value && !newItem.value.value) return
117
+ const updated = [...items.value, { ...newItem.value }]
118
+ emit('update:modelValue', updated)
119
+ newItem.value = {
120
+ key: '',
121
+ value: isTextMode.value ? '' : (props.min + props.max) / 2
122
+ }
123
+ }
124
+
125
+ function removeItem(index) {
126
+ const updated = items.value.filter((_, i) => i !== index)
127
+ emit('update:modelValue', updated)
128
+ }
129
+
130
+ function updateItemValue(index, value) {
131
+ const updated = [...items.value]
132
+ updated[index] = { ...updated[index], value }
133
+ emit('update:modelValue', updated)
134
+ }
135
+ </script>
136
+
137
+ <template>
138
+ <div class="form-field">
139
+ <label>{{ label }}</label>
140
+ <div class="kv-list">
141
+ <div v-if="items.length === 0" class="kv-empty">
142
+ No items added yet
143
+ </div>
144
+ <div v-for="(item, index) in items" :key="index" class="kv-item">
145
+ <span class="kv-key">{{ item.key }}</span>
146
+ <div class="kv-value-container">
147
+ <!-- Text mode: show text input -->
148
+ <template v-if="isTextMode">
149
+ <InputText
150
+ :modelValue="item.value"
151
+ @update:modelValue="(v) => updateItemValue(index, v)"
152
+ class="kv-text-input"
153
+ />
154
+ </template>
155
+ <!-- Number mode: show slider -->
156
+ <template v-else>
157
+ <span
158
+ class="kv-value"
159
+ :class="{
160
+ negative: colorize && item.value < 0,
161
+ positive: colorize && item.value > 0
162
+ }"
163
+ >
164
+ {{ formatValue(item.value) }}
165
+ </span>
166
+ <Slider
167
+ :modelValue="item.value"
168
+ @update:modelValue="(v) => updateItemValue(index, v)"
169
+ :min="min"
170
+ :max="max"
171
+ :step="step"
172
+ style="width: 100px"
173
+ />
174
+ </template>
175
+ </div>
176
+ <Button
177
+ icon="pi pi-times"
178
+ severity="danger"
179
+ text
180
+ rounded
181
+ size="small"
182
+ @click="removeItem(index)"
183
+ />
184
+ </div>
185
+ </div>
186
+ <div class="kv-add">
187
+ <Button
188
+ icon="pi pi-plus"
189
+ @click="addItem"
190
+ :disabled="!newItem.key || (isTextMode && !newItem.value)"
191
+ v-tooltip.top="'Add item'"
192
+ />
193
+ <!-- With autocomplete suggestions -->
194
+ <AutoComplete
195
+ v-if="hasKeySuggestions"
196
+ v-model="newItem.key"
197
+ :suggestions="filteredKeySuggestions"
198
+ @complete="searchKeys"
199
+ :placeholder="keyPlaceholder"
200
+ class="kv-add-input"
201
+ @keyup.enter="addItem"
202
+ />
203
+ <!-- Without suggestions: plain input -->
204
+ <InputText
205
+ v-else
206
+ v-model="newItem.key"
207
+ :placeholder="keyPlaceholder"
208
+ class="kv-add-input"
209
+ @keyup.enter="addItem"
210
+ />
211
+ <!-- Text mode: show text input for value -->
212
+ <template v-if="isTextMode">
213
+ <InputText
214
+ v-model="newItem.value"
215
+ :placeholder="valuePlaceholder"
216
+ class="kv-add-value"
217
+ @keyup.enter="addItem"
218
+ />
219
+ </template>
220
+ <!-- Number mode: show slider -->
221
+ <template v-else>
222
+ <div class="kv-slider">
223
+ <span>{{ formatValue(newItem.value) }}</span>
224
+ <Slider v-model="newItem.value" :min="min" :max="max" :step="step" style="width: 100px" />
225
+ </div>
226
+ </template>
227
+ </div>
228
+ <small v-if="help" class="form-field-help">{{ help }}</small>
229
+ </div>
230
+ </template>
231
+
232
+ <style scoped>
233
+ .kv-list {
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: 0.5rem;
237
+ margin-bottom: 0.5rem;
238
+ }
239
+
240
+ .kv-item {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 0.5rem;
244
+ padding: 0.5rem;
245
+ background: var(--p-surface-50);
246
+ border-radius: 0.25rem;
247
+ }
248
+
249
+ .kv-key {
250
+ font-weight: 500;
251
+ min-width: 120px;
252
+ }
253
+
254
+ .kv-value-container {
255
+ display: flex;
256
+ align-items: center;
257
+ gap: 0.5rem;
258
+ flex: 1;
259
+ }
260
+
261
+ .kv-value {
262
+ min-width: 50px;
263
+ text-align: right;
264
+ font-family: monospace;
265
+ }
266
+
267
+ .kv-value.negative {
268
+ color: var(--p-red-500);
269
+ }
270
+
271
+ .kv-value.positive {
272
+ color: var(--p-green-500);
273
+ }
274
+
275
+ .kv-add {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 0.5rem;
279
+ }
280
+
281
+ .kv-add-input {
282
+ flex: 1;
283
+ }
284
+
285
+ .kv-add-value {
286
+ flex: 1;
287
+ min-width: 150px;
288
+ }
289
+
290
+ .kv-text-input {
291
+ flex: 1;
292
+ }
293
+
294
+ .kv-slider {
295
+ display: flex;
296
+ align-items: center;
297
+ gap: 0.5rem;
298
+ }
299
+
300
+ .kv-slider span {
301
+ min-width: 50px;
302
+ text-align: right;
303
+ font-family: monospace;
304
+ }
305
+
306
+ .kv-empty {
307
+ padding: 0.75rem;
308
+ color: var(--p-text-secondary);
309
+ font-style: italic;
310
+ background: var(--p-surface-50);
311
+ border-radius: 0.25rem;
312
+ text-align: center;
313
+ }
314
+ </style>
@@ -0,0 +1,245 @@
1
+ <script setup>
2
+ /**
3
+ * LanguageEditor - Reusable component for editing language capabilities
4
+ *
5
+ * Props:
6
+ * - modelValue: Array of {code, fluency, primary} objects
7
+ * - label: Field label
8
+ * - help: Help text
9
+ */
10
+
11
+ import { ref, computed } from 'vue'
12
+ import Button from 'primevue/button'
13
+ import InputText from 'primevue/inputtext'
14
+ import Slider from 'primevue/slider'
15
+ import Checkbox from 'primevue/checkbox'
16
+
17
+ const props = defineProps({
18
+ modelValue: {
19
+ type: Array,
20
+ default: () => []
21
+ },
22
+ label: {
23
+ type: String,
24
+ default: 'Languages'
25
+ },
26
+ help: {
27
+ type: String,
28
+ default: ''
29
+ }
30
+ })
31
+
32
+ const emit = defineEmits(['update:modelValue'])
33
+
34
+ const languages = computed({
35
+ get: () => props.modelValue || [],
36
+ set: (value) => emit('update:modelValue', value)
37
+ })
38
+
39
+ const newLanguage = ref({ code: '', fluency: 0.8, primary: false })
40
+
41
+ function addLanguage() {
42
+ if (!newLanguage.value.code) return
43
+ let updated = [...languages.value]
44
+
45
+ // If this is the first language, make it primary
46
+ if (updated.length === 0) {
47
+ newLanguage.value.primary = true
48
+ }
49
+
50
+ // If new language is primary, remove primary from others
51
+ if (newLanguage.value.primary) {
52
+ updated = updated.map(l => ({ ...l, primary: false }))
53
+ }
54
+
55
+ updated.push({ ...newLanguage.value })
56
+ emit('update:modelValue', updated)
57
+ newLanguage.value = { code: '', fluency: 0.8, primary: false }
58
+ }
59
+
60
+ function removeLanguage(index) {
61
+ const updated = [...languages.value]
62
+ const wasPrimary = updated[index].primary
63
+ updated.splice(index, 1)
64
+ // If we removed the primary, make the first one primary
65
+ if (wasPrimary && updated.length > 0) {
66
+ updated[0].primary = true
67
+ }
68
+ emit('update:modelValue', updated)
69
+ }
70
+
71
+ function setPrimaryLanguage(index) {
72
+ const updated = languages.value.map((l, i) => ({
73
+ ...l,
74
+ primary: i === index
75
+ }))
76
+ emit('update:modelValue', updated)
77
+ }
78
+
79
+ function updateFluency(index, fluency) {
80
+ const updated = [...languages.value]
81
+ updated[index] = { ...updated[index], fluency }
82
+ emit('update:modelValue', updated)
83
+ }
84
+ </script>
85
+
86
+ <template>
87
+ <div class="form-field">
88
+ <label>{{ label }}</label>
89
+ <div class="lang-list">
90
+ <div v-if="languages.length === 0" class="lang-empty">
91
+ No languages added yet
92
+ </div>
93
+ <div v-for="(lang, index) in languages" :key="index" class="lang-item">
94
+ <span class="lang-code">{{ lang.code.toUpperCase() }}</span>
95
+ <div class="lang-fluency-container">
96
+ <span class="lang-fluency">{{ (lang.fluency * 100).toFixed(0) }}%</span>
97
+ <Slider
98
+ :modelValue="lang.fluency"
99
+ @update:modelValue="(v) => updateFluency(index, v)"
100
+ :min="0.1"
101
+ :max="1"
102
+ :step="0.1"
103
+ style="width: 100px"
104
+ />
105
+ </div>
106
+ <Button
107
+ v-if="lang.primary"
108
+ icon="pi pi-star-fill"
109
+ severity="warning"
110
+ text
111
+ rounded
112
+ size="small"
113
+ v-tooltip.top="'Primary language'"
114
+ />
115
+ <Button
116
+ v-else
117
+ icon="pi pi-star"
118
+ severity="secondary"
119
+ text
120
+ rounded
121
+ size="small"
122
+ @click="setPrimaryLanguage(index)"
123
+ v-tooltip.top="'Set as primary'"
124
+ />
125
+ <Button
126
+ icon="pi pi-times"
127
+ severity="danger"
128
+ text
129
+ rounded
130
+ size="small"
131
+ @click="removeLanguage(index)"
132
+ />
133
+ </div>
134
+ </div>
135
+ <div class="lang-add">
136
+ <Button
137
+ icon="pi pi-plus"
138
+ @click="addLanguage"
139
+ :disabled="!newLanguage.code"
140
+ v-tooltip.top="'Add language'"
141
+ />
142
+ <InputText
143
+ v-model="newLanguage.code"
144
+ placeholder="Code (e.g., en, fr)"
145
+ class="lang-add-input"
146
+ @keyup.enter="addLanguage"
147
+ />
148
+ <div class="lang-slider">
149
+ <span>{{ (newLanguage.fluency * 100).toFixed(0) }}%</span>
150
+ <Slider v-model="newLanguage.fluency" :min="0.1" :max="1" :step="0.1" style="width: 80px" />
151
+ </div>
152
+ <div class="lang-primary-toggle">
153
+ <Checkbox v-model="newLanguage.primary" :binary="true" inputId="newLangPrimary" />
154
+ <label for="newLangPrimary" class="lang-primary-label">Primary</label>
155
+ </div>
156
+ </div>
157
+ <small v-if="help" class="form-field-help">{{ help }}</small>
158
+ </div>
159
+ </template>
160
+
161
+ <style scoped>
162
+ .lang-list {
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 0.5rem;
166
+ margin-bottom: 0.5rem;
167
+ }
168
+
169
+ .lang-item {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 0.5rem;
173
+ padding: 0.5rem;
174
+ background: var(--p-surface-50);
175
+ border-radius: 0.25rem;
176
+ }
177
+
178
+ .lang-code {
179
+ font-weight: 600;
180
+ min-width: 40px;
181
+ padding: 0.25rem 0.5rem;
182
+ background: var(--p-primary-100);
183
+ color: var(--p-primary-700);
184
+ border-radius: 0.25rem;
185
+ text-align: center;
186
+ }
187
+
188
+ .lang-fluency-container {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 0.5rem;
192
+ flex: 1;
193
+ }
194
+
195
+ .lang-fluency {
196
+ min-width: 40px;
197
+ text-align: right;
198
+ font-family: monospace;
199
+ color: var(--p-text-secondary);
200
+ }
201
+
202
+ .lang-add {
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 0.5rem;
206
+ }
207
+
208
+ .lang-add-input {
209
+ flex: 1;
210
+ max-width: 150px;
211
+ }
212
+
213
+ .lang-slider {
214
+ display: flex;
215
+ align-items: center;
216
+ gap: 0.5rem;
217
+ }
218
+
219
+ .lang-slider span {
220
+ min-width: 40px;
221
+ text-align: right;
222
+ font-family: monospace;
223
+ }
224
+
225
+ .lang-empty {
226
+ padding: 0.75rem;
227
+ color: var(--p-text-secondary);
228
+ font-style: italic;
229
+ background: var(--p-surface-50);
230
+ border-radius: 0.25rem;
231
+ text-align: center;
232
+ }
233
+
234
+ .lang-primary-toggle {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 0.25rem;
238
+ }
239
+
240
+ .lang-primary-label {
241
+ font-size: 0.875rem;
242
+ color: var(--p-text-secondary);
243
+ cursor: pointer;
244
+ }
245
+ </style>