@smartnet360/svelte-components 0.0.17 → 0.0.19

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.
@@ -0,0 +1,207 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { createEventDispatcher } from 'svelte';
5
+ import { availableKPIs, searchKPIs } from '../../../../routes/charts/schemas/available-kpis.js';
6
+ import type { KPI } from '../charts.model.js';
7
+
8
+ interface Props {
9
+ show: boolean;
10
+ }
11
+
12
+ let { show }: Props = $props();
13
+
14
+ const dispatch = createEventDispatcher();
15
+
16
+ let searchQuery = $state('');
17
+ let selectedKPI = $state<KPI | null>(null);
18
+
19
+ let filteredKPIs = $derived(
20
+ searchQuery ? searchKPIs(searchQuery) : availableKPIs
21
+ );
22
+
23
+ function handleSelect(kpi: KPI) {
24
+ selectedKPI = kpi;
25
+ }
26
+
27
+ function handleConfirm() {
28
+ if (selectedKPI) {
29
+ dispatch('select', selectedKPI);
30
+ handleClose();
31
+ }
32
+ }
33
+
34
+ function handleClose() {
35
+ searchQuery = '';
36
+ selectedKPI = null;
37
+ dispatch('close');
38
+ }
39
+
40
+ function handleKeydown(e: KeyboardEvent) {
41
+ if (e.key === 'Escape') {
42
+ handleClose();
43
+ } else if (e.key === 'Enter' && selectedKPI) {
44
+ handleConfirm();
45
+ }
46
+ }
47
+ </script>
48
+
49
+ <svelte:window on:keydown={handleKeydown} />
50
+
51
+ {#if show}
52
+ <!-- Bootstrap Modal -->
53
+ <div class="modal fade show d-block" tabindex="-1" role="dialog" style="background-color: rgba(0,0,0,0.5);">
54
+ <div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
55
+ <div class="modal-content">
56
+ <!-- Header -->
57
+ <div class="modal-header">
58
+ <h5 class="modal-title">
59
+ <i class="bi bi-search me-2"></i>
60
+ Select KPI
61
+ </h5>
62
+ <button type="button" class="btn-close" onclick={handleClose}></button>
63
+ </div>
64
+
65
+ <!-- Body -->
66
+ <div class="modal-body">
67
+ <!-- Search Input -->
68
+ <div class="mb-3">
69
+ <div class="input-group">
70
+ <span class="input-group-text">
71
+ <i class="bi bi-search"></i>
72
+ </span>
73
+ <input
74
+ type="text"
75
+ class="form-control"
76
+ placeholder="Search KPIs by name or raw name..."
77
+ bind:value={searchQuery}
78
+ autofocus
79
+ />
80
+ {#if searchQuery}
81
+ <button
82
+ class="btn btn-outline-secondary"
83
+ onclick={() => (searchQuery = '')}
84
+ >
85
+ <i class="bi bi-x"></i>
86
+ </button>
87
+ {/if}
88
+ </div>
89
+ <small class="text-muted">
90
+ Showing {filteredKPIs.length} of {availableKPIs.length} KPIs
91
+ </small>
92
+ </div>
93
+
94
+ <!-- KPI List -->
95
+ <div class="kpi-list-container">
96
+ {#if filteredKPIs.length > 0}
97
+ <div class="list-group">
98
+ {#each filteredKPIs as kpi}
99
+ <button
100
+ class="list-group-item list-group-item-action"
101
+ class:active={selectedKPI?.rawName === kpi.rawName}
102
+ onclick={() => handleSelect(kpi)}
103
+ >
104
+ <div class="d-flex w-100 justify-content-between align-items-start">
105
+ <div class="flex-grow-1">
106
+ <h6 class="mb-1">{kpi.name}</h6>
107
+ <p class="mb-1 text-muted small">
108
+ <code>{kpi.rawName}</code>
109
+ </p>
110
+ <div class="d-flex gap-2 align-items-center">
111
+ <span class="badge bg-secondary">{kpi.scale}</span>
112
+ <span class="badge bg-info">{kpi.unit}</span>
113
+ {#if kpi.color}
114
+ <span class="color-preview" style="background-color: {kpi.color}"></span>
115
+ {/if}
116
+ </div>
117
+ </div>
118
+ {#if selectedKPI?.rawName === kpi.rawName}
119
+ <i class="bi bi-check-circle-fill text-success fs-4"></i>
120
+ {/if}
121
+ </div>
122
+ </button>
123
+ {/each}
124
+ </div>
125
+ {:else}
126
+ <div class="alert alert-warning">
127
+ <i class="bi bi-exclamation-triangle me-2"></i>
128
+ No KPIs found matching "{searchQuery}"
129
+ </div>
130
+ {/if}
131
+ </div>
132
+ </div>
133
+
134
+ <!-- Footer -->
135
+ <div class="modal-footer">
136
+ <button type="button" class="btn btn-secondary" onclick={handleClose}>
137
+ Cancel
138
+ </button>
139
+ <button
140
+ type="button"
141
+ class="btn btn-primary"
142
+ onclick={handleConfirm}
143
+ disabled={!selectedKPI}
144
+ >
145
+ <i class="bi bi-check-lg me-1"></i>
146
+ Add KPI
147
+ </button>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ {/if}
153
+
154
+ <style>
155
+ .modal {
156
+ overflow-y: auto;
157
+ }
158
+
159
+ .kpi-list-container {
160
+ max-height: 400px;
161
+ overflow-y: auto;
162
+ }
163
+
164
+ .list-group-item {
165
+ cursor: pointer;
166
+ transition: all 0.15s;
167
+ }
168
+
169
+ .list-group-item:hover:not(.active) {
170
+ background-color: #f8f9fa;
171
+ }
172
+
173
+ .list-group-item.active {
174
+ background-color: #0d6efd;
175
+ border-color: #0d6efd;
176
+ }
177
+
178
+ .list-group-item.active h6,
179
+ .list-group-item.active p,
180
+ .list-group-item.active .text-muted {
181
+ color: white !important;
182
+ }
183
+
184
+ .list-group-item.active code {
185
+ background-color: rgba(255, 255, 255, 0.2);
186
+ color: white;
187
+ }
188
+
189
+ .color-preview {
190
+ display: inline-block;
191
+ width: 20px;
192
+ height: 20px;
193
+ border-radius: 4px;
194
+ border: 2px solid #dee2e6;
195
+ }
196
+
197
+ code {
198
+ background-color: #f8f9fa;
199
+ padding: 0.2rem 0.4rem;
200
+ border-radius: 3px;
201
+ font-size: 0.875rem;
202
+ }
203
+
204
+ .badge {
205
+ font-size: 0.75rem;
206
+ }
207
+ </style>
@@ -0,0 +1,24 @@
1
+ interface Props {
2
+ show: boolean;
3
+ }
4
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
6
+ $$bindings?: Bindings;
7
+ } & Exports;
8
+ (internal: unknown, props: Props & {
9
+ $$events?: Events;
10
+ $$slots?: Slots;
11
+ }): Exports & {
12
+ $set?: any;
13
+ $on?: any;
14
+ };
15
+ z_$$bindings?: Bindings;
16
+ }
17
+ declare const KpiPicker: $$__sveltets_2_IsomorphicComponent<Props, {
18
+ select: CustomEvent<any>;
19
+ close: CustomEvent<any>;
20
+ } & {
21
+ [evt: string]: CustomEvent<any>;
22
+ }, {}, {}, "">;
23
+ type KpiPicker = InstanceType<typeof KpiPicker>;
24
+ export default KpiPicker;
@@ -0,0 +1,180 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { editorStore, currentLayout, selection } from './editorState.js';
5
+
6
+ function handleSelectLayout() {
7
+ editorStore.select({ type: 'layout' });
8
+ }
9
+
10
+ function handleSelectSection(sectionId: string) {
11
+ editorStore.select({ type: 'section', sectionId });
12
+ }
13
+
14
+ function handleSelectChart(sectionId: string, chartIndex: number) {
15
+ editorStore.select({ type: 'chart', sectionId, chartIndex });
16
+ }
17
+
18
+ function handleAddSection() {
19
+ editorStore.addSection();
20
+ }
21
+
22
+ function handleAddChart(sectionId: string) {
23
+ editorStore.addChart(sectionId);
24
+ }
25
+
26
+ function isSelected(type: string, sectionId?: string, chartIndex?: number): boolean {
27
+ if ($selection.type !== type) return false;
28
+ if (type === 'section') return $selection.sectionId === sectionId;
29
+ if (type === 'chart') return $selection.sectionId === sectionId && $selection.chartIndex === chartIndex;
30
+ return type === 'layout';
31
+ }
32
+ </script>
33
+
34
+ <div class="tree-view h-100 d-flex flex-column">
35
+ <!-- Header -->
36
+ <div class="p-3 border-bottom bg-light">
37
+ <h6 class="mb-0">
38
+ <i class="bi bi-diagram-3"></i> Structure
39
+ </h6>
40
+ </div>
41
+
42
+ <!-- Tree Content -->
43
+ <div class="flex-grow-1 overflow-auto p-2">
44
+ {#if $currentLayout}
45
+ <!-- Layout Node -->
46
+ <div class="list-group mb-2">
47
+ <div
48
+ class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
49
+ class:active={isSelected('layout')}
50
+ role="button"
51
+ tabindex="0"
52
+ onclick={handleSelectLayout}
53
+ onkeydown={(e) => e.key === 'Enter' && handleSelectLayout()}
54
+ >
55
+ <div>
56
+ <i class="bi bi-file-earmark-text me-2"></i>
57
+ <strong>{$currentLayout.layoutName}</strong>
58
+ </div>
59
+ <button
60
+ class="btn btn-sm btn-primary"
61
+ onclick={(e) => {
62
+ e.stopPropagation();
63
+ handleAddSection();
64
+ }}
65
+ title="Add Section"
66
+ >
67
+ <i class="bi bi-plus-lg me-1"></i>
68
+ <span>Section</span>
69
+ </button>
70
+ </div>
71
+ </div>
72
+
73
+ <!-- Sections -->
74
+ {#each $currentLayout.sections as section, sIdx}
75
+ <div class="ms-3 mb-2">
76
+ <div class="list-group">
77
+ <!-- Section Node -->
78
+ <div
79
+ class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
80
+ class:active={isSelected('section', section.id)}
81
+ role="button"
82
+ tabindex="0"
83
+ onclick={() => handleSelectSection(section.id)}
84
+ onkeydown={(e) => e.key === 'Enter' && handleSelectSection(section.id)}
85
+ >
86
+ <div class="flex-grow-1">
87
+ <i class="bi bi-grid-3x3-gap me-2"></i>
88
+ <span>{section.title}</span>
89
+ <span class="badge bg-secondary ms-2">{section.grid || '2x2'}</span>
90
+ </div>
91
+ <button
92
+ class="btn btn-sm btn-success"
93
+ onclick={(e) => {
94
+ e.stopPropagation();
95
+ handleAddChart(section.id);
96
+ }}
97
+ title="Add Chart"
98
+ >
99
+ <i class="bi bi-plus-lg me-1"></i>
100
+ <span>Chart</span>
101
+ </button>
102
+ </div>
103
+
104
+ <!-- Charts in Section -->
105
+ {#if section.charts && section.charts.length > 0}
106
+ <div class="ms-3 mt-1">
107
+ {#each section.charts as chart, cIdx}
108
+ <button
109
+ class="list-group-item list-group-item-action mb-1"
110
+ class:active={isSelected('chart', section.id, cIdx)}
111
+ onclick={() => handleSelectChart(section.id, cIdx)}
112
+ >
113
+ <div class="d-flex align-items-start">
114
+ <i class="bi bi-bar-chart me-2 mt-1"></i>
115
+ <div class="flex-grow-1">
116
+ <div class="fw-bold">{chart.title}</div>
117
+ <small class="text-muted">
118
+ {#if chart.pos}
119
+ <span class="badge bg-info me-1">Pos: {chart.pos}</span>
120
+ {/if}
121
+ <span class="badge bg-success me-1">L: {chart.yLeft.length}</span>
122
+ <span class="badge bg-warning">R: {chart.yRight.length}</span>
123
+ </small>
124
+ </div>
125
+ </div>
126
+ </button>
127
+ {/each}
128
+ </div>
129
+ {:else}
130
+ <div class="ms-3 mt-1">
131
+ <div class="text-muted small p-2">
132
+ <i class="bi bi-info-circle me-1"></i>
133
+ No charts yet
134
+ </div>
135
+ </div>
136
+ {/if}
137
+ </div>
138
+ </div>
139
+ {:else}
140
+ <div class="alert alert-info small m-2">
141
+ <i class="bi bi-info-circle me-1"></i>
142
+ No sections yet. Click the blue <strong>"+ Section"</strong> button above.
143
+ </div>
144
+ {/each}
145
+ {/if}
146
+ </div>
147
+ </div>
148
+
149
+ <style>
150
+ .tree-view {
151
+ font-size: 0.9rem;
152
+ }
153
+
154
+ .list-group-item {
155
+ padding: 0.5rem 0.75rem;
156
+ border-radius: 0.25rem;
157
+ margin-bottom: 0.25rem;
158
+ cursor: pointer;
159
+ }
160
+
161
+ .list-group-item.active {
162
+ background-color: #0d6efd;
163
+ border-color: #0d6efd;
164
+ color: white;
165
+ }
166
+
167
+ .list-group-item:not(.active):hover {
168
+ background-color: #f8f9fa;
169
+ }
170
+
171
+ .badge {
172
+ font-size: 0.7rem;
173
+ padding: 0.25rem 0.5rem;
174
+ }
175
+
176
+ .btn-sm {
177
+ padding: 0.15rem 0.4rem;
178
+ font-size: 0.75rem;
179
+ }
180
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const LayoutTreeView: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type LayoutTreeView = ReturnType<typeof LayoutTreeView>;
3
+ export default LayoutTreeView;
@@ -0,0 +1,286 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { createEventDispatcher } from 'svelte';
5
+ import { editorStore, currentLayout, selection, selectedItem } from './editorState.js';
6
+ import type { ChartGrid, Section, Chart } from '../charts.model.js';
7
+
8
+ const dispatch = createEventDispatcher();
9
+
10
+ const gridOptions: ChartGrid[] = ['2x2', '3x3', '4x4', '1x2', '1x4', '1x8'];
11
+
12
+ function handleOpenKPIPicker(side: 'yLeft' | 'yRight') {
13
+ if ($selection.type === 'chart' && $selection.sectionId && $selection.chartIndex !== undefined) {
14
+ dispatch('openkpipicker', {
15
+ sectionId: $selection.sectionId,
16
+ chartIndex: $selection.chartIndex,
17
+ side
18
+ });
19
+ }
20
+ }
21
+
22
+ function handleRemoveKPI(side: 'yLeft' | 'yRight', kpiIndex: number) {
23
+ if ($selection.type === 'chart' && $selection.sectionId && $selection.chartIndex !== undefined) {
24
+ editorStore.removeKPI($selection.sectionId, $selection.chartIndex, side, kpiIndex);
25
+ }
26
+ }
27
+
28
+ function handleDeleteSection() {
29
+ if ($selection.sectionId && confirm('Delete this section and all its charts?')) {
30
+ editorStore.deleteSection($selection.sectionId);
31
+ }
32
+ }
33
+
34
+ function handleDeleteChart() {
35
+ if ($selection.sectionId && $selection.chartIndex !== undefined && confirm('Delete this chart?')) {
36
+ editorStore.deleteChart($selection.sectionId, $selection.chartIndex);
37
+ }
38
+ }
39
+ </script>
40
+
41
+ <div class="properties-panel h-100 d-flex flex-column">
42
+ <!-- Header -->
43
+ <div class="p-3 border-bottom bg-light">
44
+ <h6 class="mb-0">
45
+ <i class="bi bi-sliders"></i> Properties
46
+ </h6>
47
+ </div>
48
+
49
+ <!-- Content -->
50
+ <div class="flex-grow-1 overflow-auto p-3">
51
+ {#if !$selection.type}
52
+ <div class="text-center text-muted mt-5">
53
+ <i class="bi bi-hand-index display-4"></i>
54
+ <p class="mt-3">Select an item to edit its properties</p>
55
+ </div>
56
+ {:else if $selection.type === 'layout'}
57
+ <!-- Layout Properties -->
58
+ <div class="mb-3">
59
+ <label class="form-label fw-bold">Layout Name</label>
60
+ <input
61
+ type="text"
62
+ class="form-control"
63
+ value={$currentLayout?.layoutName || ''}
64
+ oninput={(e) => editorStore.updateLayoutName(e.currentTarget.value)}
65
+ />
66
+ </div>
67
+
68
+ <div class="alert alert-info small">
69
+ <i class="bi bi-info-circle me-1"></i>
70
+ <strong>{$currentLayout?.sections.length || 0}</strong> sections in this layout
71
+ </div>
72
+ {:else if $selection.type === 'section'}
73
+ {@const section = $selectedItem as Section}
74
+ {#if section}
75
+ <!-- Section Properties -->
76
+ <div class="d-flex justify-content-between align-items-center mb-3">
77
+ <h6 class="mb-0">Section</h6>
78
+ <button class="btn btn-sm btn-outline-danger" onclick={handleDeleteSection}>
79
+ <i class="bi bi-trash"></i>
80
+ </button>
81
+ </div>
82
+
83
+ <div class="mb-3">
84
+ <label class="form-label">Title</label>
85
+ <input
86
+ type="text"
87
+ class="form-control"
88
+ value={section.title}
89
+ oninput={(e) =>
90
+ editorStore.updateSection(section.id, { title: e.currentTarget.value })}
91
+ />
92
+ </div>
93
+
94
+ <div class="mb-3">
95
+ <label class="form-label">Section ID</label>
96
+ <input
97
+ type="text"
98
+ class="form-control"
99
+ value={section.id}
100
+ oninput={(e) => editorStore.updateSection(section.id, { id: e.currentTarget.value })}
101
+ />
102
+ <small class="form-text text-muted">Unique identifier for this section</small>
103
+ </div>
104
+
105
+ <div class="mb-3">
106
+ <label class="form-label">Grid Layout</label>
107
+ <select
108
+ class="form-select"
109
+ value={section.grid || '2x2'}
110
+ onchange={(e) => editorStore.updateSection(section.id, { grid: e.currentTarget.value as ChartGrid })}
111
+ >
112
+ {#each gridOptions as grid}
113
+ <option value={grid}>{grid}</option>
114
+ {/each}
115
+ </select>
116
+ </div>
117
+
118
+ <div class="alert alert-info small">
119
+ <i class="bi bi-bar-chart me-1"></i>
120
+ <strong>{section.charts?.length || 0}</strong> charts in this section
121
+ </div>
122
+ {/if}
123
+ {:else if $selection.type === 'chart'}
124
+ {@const chart = $selectedItem as Chart}
125
+ {#if chart && $selection.sectionId && $selection.chartIndex !== undefined}
126
+ <!-- Chart Properties -->
127
+ <div class="d-flex justify-content-between align-items-center mb-3">
128
+ <h6 class="mb-0">Chart</h6>
129
+ <button class="btn btn-sm btn-outline-danger" onclick={handleDeleteChart}>
130
+ <i class="bi bi-trash"></i>
131
+ </button>
132
+ </div>
133
+
134
+ <div class="mb-3">
135
+ <label class="form-label">Title</label>
136
+ <input
137
+ type="text"
138
+ class="form-control"
139
+ value={chart.title}
140
+ oninput={(e) =>
141
+ editorStore.updateChart($selection.sectionId!, $selection.chartIndex!, {
142
+ title: e.currentTarget.value
143
+ })}
144
+ />
145
+ </div>
146
+
147
+ <div class="mb-3">
148
+ <label class="form-label">Position (optional)</label>
149
+ <input
150
+ type="number"
151
+ class="form-control"
152
+ min="1"
153
+ max="9"
154
+ value={chart.pos || ''}
155
+ oninput={(e) => {
156
+ const val = e.currentTarget.value;
157
+ const numVal = val ? parseInt(val) : undefined;
158
+ editorStore.updateChart($selection.sectionId!, $selection.chartIndex!, {
159
+ pos: (numVal && numVal >= 1 && numVal <= 9) ? numVal as any : undefined
160
+ });
161
+ }}
162
+ />
163
+ <small class="form-text text-muted">Grid position (1-9)</small>
164
+ </div>
165
+
166
+ <!-- Left Y-Axis KPIs -->
167
+ <div class="mb-3">
168
+ <div class="d-flex justify-content-between align-items-center mb-2">
169
+ <label class="form-label mb-0">Left Y-Axis KPIs</label>
170
+ <button
171
+ class="btn btn-sm btn-outline-success"
172
+ onclick={() => handleOpenKPIPicker('yLeft')}
173
+ >
174
+ <i class="bi bi-plus"></i> Add
175
+ </button>
176
+ </div>
177
+
178
+ {#if chart.yLeft.length > 0}
179
+ <div class="list-group">
180
+ {#each chart.yLeft as kpi, idx}
181
+ <div class="list-group-item d-flex justify-content-between align-items-start">
182
+ <div class="flex-grow-1">
183
+ <div class="fw-bold">{kpi.name}</div>
184
+ <small class="text-muted">
185
+ {kpi.rawName} | {kpi.unit}
186
+ {#if kpi.color}
187
+ <span class="color-badge" style="background-color: {kpi.color}"></span>
188
+ {/if}
189
+ </small>
190
+ </div>
191
+ <button
192
+ class="btn btn-sm btn-outline-danger"
193
+ onclick={() => handleRemoveKPI('yLeft', idx)}
194
+ >
195
+ <i class="bi bi-x"></i>
196
+ </button>
197
+ </div>
198
+ {/each}
199
+ </div>
200
+ {:else}
201
+ <div class="alert alert-secondary small mb-0">
202
+ No KPIs on left axis
203
+ </div>
204
+ {/if}
205
+ </div>
206
+
207
+ <!-- Right Y-Axis KPIs -->
208
+ <div class="mb-3">
209
+ <div class="d-flex justify-content-between align-items-center mb-2">
210
+ <label class="form-label mb-0">Right Y-Axis KPIs</label>
211
+ <button
212
+ class="btn btn-sm btn-outline-warning"
213
+ onclick={() => handleOpenKPIPicker('yRight')}
214
+ >
215
+ <i class="bi bi-plus"></i> Add
216
+ </button>
217
+ </div>
218
+
219
+ {#if chart.yRight.length > 0}
220
+ <div class="list-group">
221
+ {#each chart.yRight as kpi, idx}
222
+ <div class="list-group-item d-flex justify-content-between align-items-start">
223
+ <div class="flex-grow-1">
224
+ <div class="fw-bold">{kpi.name}</div>
225
+ <small class="text-muted">
226
+ {kpi.rawName} | {kpi.unit}
227
+ {#if kpi.color}
228
+ <span class="color-badge" style="background-color: {kpi.color}"></span>
229
+ {/if}
230
+ </small>
231
+ </div>
232
+ <button
233
+ class="btn btn-sm btn-outline-danger"
234
+ onclick={() => handleRemoveKPI('yRight', idx)}
235
+ >
236
+ <i class="bi bi-x"></i>
237
+ </button>
238
+ </div>
239
+ {/each}
240
+ </div>
241
+ {:else}
242
+ <div class="alert alert-secondary small mb-0">
243
+ No KPIs on right axis
244
+ </div>
245
+ {/if}
246
+ </div>
247
+ {/if}
248
+ {/if}
249
+ </div>
250
+ </div>
251
+
252
+ <style>
253
+ .properties-panel {
254
+ font-size: 0.9rem;
255
+ }
256
+
257
+ .form-label {
258
+ font-weight: 600;
259
+ font-size: 0.85rem;
260
+ color: #495057;
261
+ }
262
+
263
+ .form-control,
264
+ .form-select {
265
+ font-size: 0.875rem;
266
+ }
267
+
268
+ .list-group-item {
269
+ padding: 0.5rem 0.75rem;
270
+ font-size: 0.85rem;
271
+ }
272
+
273
+ .color-badge {
274
+ display: inline-block;
275
+ width: 12px;
276
+ height: 12px;
277
+ border-radius: 2px;
278
+ border: 1px solid #dee2e6;
279
+ margin-left: 0.25rem;
280
+ }
281
+
282
+ .btn-sm {
283
+ padding: 0.15rem 0.4rem;
284
+ font-size: 0.75rem;
285
+ }
286
+ </style>