@codingfactory/inventory-locator-client 0.1.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 (53) hide show
  1. package/package.json +47 -0
  2. package/src/api/client.ts +31 -0
  3. package/src/components/counting/CountEntryForm.vue +833 -0
  4. package/src/components/counting/CountReport.vue +385 -0
  5. package/src/components/counting/CountSessionDetail.vue +650 -0
  6. package/src/components/counting/CountSessionList.vue +683 -0
  7. package/src/components/dashboard/InventoryDashboard.vue +670 -0
  8. package/src/components/dashboard/UnlocatedProducts.vue +468 -0
  9. package/src/components/labels/LabelBatchPrint.vue +528 -0
  10. package/src/components/labels/LabelPreview.vue +293 -0
  11. package/src/components/locations/InventoryLocatorShell.vue +408 -0
  12. package/src/components/locations/LocationBreadcrumb.vue +144 -0
  13. package/src/components/locations/LocationCodeBadge.vue +46 -0
  14. package/src/components/locations/LocationDetail.vue +884 -0
  15. package/src/components/locations/LocationForm.vue +360 -0
  16. package/src/components/locations/LocationSearchInput.vue +428 -0
  17. package/src/components/locations/LocationTree.vue +156 -0
  18. package/src/components/locations/LocationTreeNode.vue +280 -0
  19. package/src/components/locations/LocationTypeIcon.vue +58 -0
  20. package/src/components/products/LocationProductAdd.vue +637 -0
  21. package/src/components/products/LocationProductList.vue +547 -0
  22. package/src/components/products/ProductLocationList.vue +215 -0
  23. package/src/components/products/QuickMoveModal.vue +592 -0
  24. package/src/components/scanning/ScanHistory.vue +146 -0
  25. package/src/components/scanning/ScanResult.vue +350 -0
  26. package/src/components/scanning/ScannerOverlay.vue +696 -0
  27. package/src/components/shared/InvBadge.vue +71 -0
  28. package/src/components/shared/InvButton.vue +206 -0
  29. package/src/components/shared/InvCard.vue +254 -0
  30. package/src/components/shared/InvEmptyState.vue +132 -0
  31. package/src/components/shared/InvInput.vue +125 -0
  32. package/src/components/shared/InvModal.vue +296 -0
  33. package/src/components/shared/InvSelect.vue +155 -0
  34. package/src/components/shared/InvTable.vue +288 -0
  35. package/src/composables/useCountSessions.ts +184 -0
  36. package/src/composables/useLabelPrinting.ts +71 -0
  37. package/src/composables/useLocationBreadcrumbs.ts +19 -0
  38. package/src/composables/useLocationProducts.ts +125 -0
  39. package/src/composables/useLocationSearch.ts +46 -0
  40. package/src/composables/useLocations.ts +159 -0
  41. package/src/composables/useMovements.ts +71 -0
  42. package/src/composables/useScanner.ts +83 -0
  43. package/src/env.d.ts +7 -0
  44. package/src/index.ts +46 -0
  45. package/src/plugin.ts +14 -0
  46. package/src/stores/countStore.ts +95 -0
  47. package/src/stores/locationStore.ts +113 -0
  48. package/src/stores/scannerStore.ts +51 -0
  49. package/src/types/index.ts +216 -0
  50. package/src/utils/codeFormatter.ts +29 -0
  51. package/src/utils/locationIcons.ts +64 -0
  52. package/tsconfig.json +21 -0
  53. package/vite.config.ts +37 -0
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <span
3
+ class="inv-badge"
4
+ :class="[`inv-badge--${variant}`, `inv-badge--${size}`]"
5
+ >
6
+ <slot />
7
+ </span>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ withDefaults(defineProps<{
12
+ variant?: 'default' | 'success' | 'warning' | 'error' | 'info' | 'muted'
13
+ size?: 'sm' | 'md'
14
+ }>(), {
15
+ variant: 'default',
16
+ size: 'sm',
17
+ })
18
+ </script>
19
+
20
+ <style scoped>
21
+ .inv-badge {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ border-radius: 9999px;
25
+ font-weight: 500;
26
+ line-height: 1;
27
+ white-space: nowrap;
28
+ }
29
+
30
+ /* Sizes */
31
+ .inv-badge--sm {
32
+ padding: 2px var(--space-2, 0.5rem);
33
+ font-size: var(--text-xs, 0.75rem);
34
+ }
35
+
36
+ .inv-badge--md {
37
+ padding: var(--space-1, 0.25rem) var(--space-3, 0.75rem);
38
+ font-size: var(--text-sm, 0.875rem);
39
+ }
40
+
41
+ /* Variants */
42
+ .inv-badge--default {
43
+ background: color-mix(in srgb, var(--color-primary, #2563eb) 12%, transparent);
44
+ color: var(--color-primary, #2563eb);
45
+ }
46
+
47
+ .inv-badge--success {
48
+ background: color-mix(in srgb, var(--color-success, #059669) 12%, transparent);
49
+ color: var(--color-success, #059669);
50
+ }
51
+
52
+ .inv-badge--warning {
53
+ background: color-mix(in srgb, var(--color-warning, #d97706) 12%, transparent);
54
+ color: var(--color-warning, #d97706);
55
+ }
56
+
57
+ .inv-badge--error {
58
+ background: color-mix(in srgb, var(--color-error, #dc2626) 12%, transparent);
59
+ color: var(--color-error, #dc2626);
60
+ }
61
+
62
+ .inv-badge--info {
63
+ background: color-mix(in srgb, var(--color-info, #0891b2) 12%, transparent);
64
+ color: var(--color-info, #0891b2);
65
+ }
66
+
67
+ .inv-badge--muted {
68
+ background: var(--admin-content-bg);
69
+ color: var(--admin-text-tertiary);
70
+ }
71
+ </style>
@@ -0,0 +1,206 @@
1
+ <template>
2
+ <button
3
+ class="inv-btn"
4
+ :class="[
5
+ `inv-btn--${variant}`,
6
+ `inv-btn--${size}`,
7
+ { 'inv-btn--loading': loading, 'inv-btn--disabled': disabled || loading },
8
+ ]"
9
+ :type="type"
10
+ :disabled="disabled || loading"
11
+ :aria-busy="loading || undefined"
12
+ >
13
+ <span v-if="loading" class="inv-btn__spinner" aria-hidden="true">
14
+ <svg
15
+ width="16"
16
+ height="16"
17
+ viewBox="0 0 16 16"
18
+ fill="none"
19
+ >
20
+ <circle
21
+ cx="8"
22
+ cy="8"
23
+ r="6"
24
+ stroke="currentColor"
25
+ stroke-width="2"
26
+ stroke-linecap="round"
27
+ opacity="0.25"
28
+ />
29
+ <path
30
+ d="M14 8A6 6 0 0 0 8 2"
31
+ stroke="currentColor"
32
+ stroke-width="2"
33
+ stroke-linecap="round"
34
+ />
35
+ </svg>
36
+ </span>
37
+ <span v-else-if="$slots['icon-left']" class="inv-btn__icon inv-btn__icon--left">
38
+ <slot name="icon-left" />
39
+ </span>
40
+
41
+ <span class="inv-btn__label">
42
+ <slot />
43
+ </span>
44
+
45
+ <span v-if="$slots['icon-right']" class="inv-btn__icon inv-btn__icon--right">
46
+ <slot name="icon-right" />
47
+ </span>
48
+ </button>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ withDefaults(defineProps<{
53
+ variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
54
+ size?: 'sm' | 'md' | 'lg'
55
+ loading?: boolean
56
+ disabled?: boolean
57
+ type?: 'button' | 'submit'
58
+ }>(), {
59
+ variant: 'primary',
60
+ size: 'md',
61
+ loading: false,
62
+ disabled: false,
63
+ type: 'button',
64
+ })
65
+ </script>
66
+
67
+ <style scoped>
68
+ .inv-btn {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ gap: var(--space-2, 0.5rem);
73
+ border: 1px solid transparent;
74
+ border-radius: 6px;
75
+ font-weight: 500;
76
+ line-height: 1;
77
+ cursor: pointer;
78
+ white-space: nowrap;
79
+ transition: background-color 0.15s ease, border-color 0.15s ease,
80
+ color 0.15s ease, box-shadow 0.15s ease;
81
+ user-select: none;
82
+ }
83
+
84
+ .inv-btn:focus-visible {
85
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
86
+ outline-offset: 2px;
87
+ }
88
+
89
+ /* Sizes */
90
+ .inv-btn--sm {
91
+ min-height: 36px;
92
+ padding: var(--space-1, 0.25rem) var(--space-3, 0.75rem);
93
+ font-size: var(--text-xs, 0.75rem);
94
+ }
95
+
96
+ .inv-btn--md {
97
+ min-height: 44px;
98
+ padding: var(--space-2, 0.5rem) var(--space-4, 1rem);
99
+ font-size: var(--text-sm, 0.875rem);
100
+ }
101
+
102
+ .inv-btn--lg {
103
+ min-height: 52px;
104
+ padding: var(--space-3, 0.75rem) var(--space-6, 1.5rem);
105
+ font-size: var(--text-base, 1rem);
106
+ }
107
+
108
+ /* Primary variant */
109
+ .inv-btn--primary {
110
+ background: var(--color-primary, #2563eb);
111
+ color: #fff;
112
+ border-color: var(--color-primary, #2563eb);
113
+ }
114
+
115
+ .inv-btn--primary:hover:not(:disabled) {
116
+ background: color-mix(in srgb, var(--color-primary, #2563eb) 85%, black);
117
+ border-color: color-mix(in srgb, var(--color-primary, #2563eb) 85%, black);
118
+ }
119
+
120
+ .inv-btn--primary:active:not(:disabled) {
121
+ background: color-mix(in srgb, var(--color-primary, #2563eb) 75%, black);
122
+ }
123
+
124
+ /* Secondary variant */
125
+ .inv-btn--secondary {
126
+ background: var(--admin-card-bg);
127
+ color: var(--admin-text-primary);
128
+ border-color: var(--admin-border);
129
+ }
130
+
131
+ .inv-btn--secondary:hover:not(:disabled) {
132
+ background: var(--admin-content-bg);
133
+ border-color: var(--admin-text-tertiary);
134
+ }
135
+
136
+ .inv-btn--secondary:active:not(:disabled) {
137
+ background: var(--admin-table-header-bg);
138
+ }
139
+
140
+ /* Danger variant */
141
+ .inv-btn--danger {
142
+ background: var(--color-error, #dc2626);
143
+ color: #fff;
144
+ border-color: var(--color-error, #dc2626);
145
+ }
146
+
147
+ .inv-btn--danger:hover:not(:disabled) {
148
+ background: color-mix(in srgb, var(--color-error, #dc2626) 85%, black);
149
+ border-color: color-mix(in srgb, var(--color-error, #dc2626) 85%, black);
150
+ }
151
+
152
+ .inv-btn--danger:active:not(:disabled) {
153
+ background: color-mix(in srgb, var(--color-error, #dc2626) 75%, black);
154
+ }
155
+
156
+ /* Ghost variant */
157
+ .inv-btn--ghost {
158
+ background: transparent;
159
+ color: var(--admin-text-secondary);
160
+ border-color: transparent;
161
+ }
162
+
163
+ .inv-btn--ghost:hover:not(:disabled) {
164
+ background: var(--admin-content-bg);
165
+ color: var(--admin-text-primary);
166
+ }
167
+
168
+ .inv-btn--ghost:active:not(:disabled) {
169
+ background: var(--admin-table-header-bg);
170
+ }
171
+
172
+ /* Disabled state */
173
+ .inv-btn--disabled {
174
+ opacity: 0.5;
175
+ cursor: not-allowed;
176
+ }
177
+
178
+ /* Loading */
179
+ .inv-btn--loading {
180
+ cursor: wait;
181
+ }
182
+
183
+ .inv-btn__spinner {
184
+ display: flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ animation: inv-btn-spin 0.8s linear infinite;
188
+ }
189
+
190
+ @keyframes inv-btn-spin {
191
+ from { transform: rotate(0deg); }
192
+ to { transform: rotate(360deg); }
193
+ }
194
+
195
+ .inv-btn__icon {
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ flex-shrink: 0;
200
+ }
201
+
202
+ .inv-btn__label {
203
+ display: flex;
204
+ align-items: center;
205
+ }
206
+ </style>
@@ -0,0 +1,254 @@
1
+ <template>
2
+ <div
3
+ class="inv-card"
4
+ :class="{ 'inv-card--compact': compact, 'inv-card--no-padding': noPadding }"
5
+ >
6
+ <div
7
+ v-if="title || $slots.actions"
8
+ class="inv-card__header"
9
+ :class="{ 'inv-card__header--collapsible': collapsible }"
10
+ >
11
+ <div class="inv-card__header-content">
12
+ <component
13
+ :is="collapsible ? 'button' : 'div'"
14
+ class="inv-card__title-area"
15
+ :class="{ 'inv-card__title-button': collapsible }"
16
+ :type="collapsible ? 'button' : undefined"
17
+ :aria-expanded="collapsible ? !isCollapsed : undefined"
18
+ :aria-controls="collapsible ? contentId : undefined"
19
+ @click="collapsible ? handleToggle() : undefined"
20
+ >
21
+ <svg
22
+ v-if="collapsible"
23
+ class="inv-card__chevron"
24
+ :class="{ 'inv-card__chevron--collapsed': isCollapsed }"
25
+ width="16"
26
+ height="16"
27
+ viewBox="0 0 16 16"
28
+ fill="none"
29
+ aria-hidden="true"
30
+ >
31
+ <path
32
+ d="M4 6L8 10L12 6"
33
+ stroke="currentColor"
34
+ stroke-width="2"
35
+ stroke-linecap="round"
36
+ stroke-linejoin="round"
37
+ />
38
+ </svg>
39
+ <div>
40
+ <h3 v-if="title" class="inv-card__title">{{ title }}</h3>
41
+ <p v-if="subtitle" class="inv-card__subtitle">{{ subtitle }}</p>
42
+ </div>
43
+ </component>
44
+ </div>
45
+ <div v-if="$slots.actions" class="inv-card__actions">
46
+ <slot name="actions" />
47
+ </div>
48
+ </div>
49
+
50
+ <div
51
+ v-show="!isCollapsed"
52
+ :id="contentId"
53
+ class="inv-card__body"
54
+ role="region"
55
+ :aria-labelledby="title ? titleId : undefined"
56
+ >
57
+ <div v-if="loading" class="inv-card__loading-overlay" aria-live="polite">
58
+ <div class="inv-card__shimmer" />
59
+ <div class="inv-card__shimmer inv-card__shimmer--short" />
60
+ <div class="inv-card__shimmer inv-card__shimmer--medium" />
61
+ <span class="sr-only">Loading...</span>
62
+ </div>
63
+ <slot v-else />
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <script setup lang="ts">
69
+ import { ref, watch, computed, useId } from 'vue'
70
+
71
+ const props = withDefaults(defineProps<{
72
+ title?: string
73
+ subtitle?: string
74
+ loading?: boolean
75
+ collapsible?: boolean
76
+ collapsed?: boolean
77
+ noPadding?: boolean
78
+ compact?: boolean
79
+ }>(), {
80
+ loading: false,
81
+ collapsible: false,
82
+ collapsed: false,
83
+ noPadding: false,
84
+ compact: false,
85
+ })
86
+
87
+ const emit = defineEmits<{
88
+ toggle: [collapsed: boolean]
89
+ }>()
90
+
91
+ const uid = useId()
92
+ const contentId = computed(() => `inv-card-content-${uid}`)
93
+ const titleId = computed(() => `inv-card-title-${uid}`)
94
+
95
+ const isCollapsed = ref(props.collapsed)
96
+
97
+ watch(() => props.collapsed, (val) => {
98
+ isCollapsed.value = val
99
+ })
100
+
101
+ function handleToggle() {
102
+ isCollapsed.value = !isCollapsed.value
103
+ emit('toggle', isCollapsed.value)
104
+ }
105
+ </script>
106
+
107
+ <style scoped>
108
+ .inv-card {
109
+ background: var(--admin-card-bg);
110
+ border: 1px solid var(--admin-border);
111
+ border-radius: 8px;
112
+ box-shadow: var(--shadow-sm);
113
+ overflow: hidden;
114
+ }
115
+
116
+ .inv-card__header {
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ gap: var(--space-3, 0.75rem);
121
+ padding: var(--space-4, 1rem) var(--space-5, 1.25rem);
122
+ border-bottom: 1px solid var(--admin-border);
123
+ }
124
+
125
+ .inv-card--compact .inv-card__header {
126
+ padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
127
+ }
128
+
129
+ .inv-card__header-content {
130
+ display: flex;
131
+ align-items: center;
132
+ min-width: 0;
133
+ flex: 1;
134
+ }
135
+
136
+ .inv-card__title-area {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: var(--space-2, 0.5rem);
140
+ min-width: 0;
141
+ }
142
+
143
+ .inv-card__title-button {
144
+ background: none;
145
+ border: none;
146
+ padding: 0;
147
+ margin: 0;
148
+ cursor: pointer;
149
+ color: inherit;
150
+ font: inherit;
151
+ text-align: left;
152
+ border-radius: 4px;
153
+ }
154
+
155
+ .inv-card__title-button:focus-visible {
156
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
157
+ outline-offset: 2px;
158
+ }
159
+
160
+ .inv-card__chevron {
161
+ flex-shrink: 0;
162
+ color: var(--admin-text-tertiary);
163
+ transition: transform 0.2s ease;
164
+ }
165
+
166
+ .inv-card__chevron--collapsed {
167
+ transform: rotate(-90deg);
168
+ }
169
+
170
+ .inv-card__title {
171
+ margin: 0;
172
+ font-size: var(--text-base, 1rem);
173
+ font-weight: 600;
174
+ color: var(--admin-text-primary);
175
+ line-height: 1.4;
176
+ }
177
+
178
+ .inv-card--compact .inv-card__title {
179
+ font-size: var(--text-sm, 0.875rem);
180
+ }
181
+
182
+ .inv-card__subtitle {
183
+ margin: var(--space-1, 0.25rem) 0 0;
184
+ font-size: var(--text-sm, 0.875rem);
185
+ color: var(--admin-text-secondary);
186
+ line-height: 1.4;
187
+ }
188
+
189
+ .inv-card__actions {
190
+ display: flex;
191
+ align-items: center;
192
+ gap: var(--space-2, 0.5rem);
193
+ flex-shrink: 0;
194
+ }
195
+
196
+ .inv-card__body {
197
+ padding: var(--space-5, 1.25rem);
198
+ position: relative;
199
+ }
200
+
201
+ .inv-card--compact .inv-card__body {
202
+ padding: var(--space-4, 1rem);
203
+ }
204
+
205
+ .inv-card--no-padding .inv-card__body {
206
+ padding: 0;
207
+ }
208
+
209
+ .inv-card__loading-overlay {
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: var(--space-3, 0.75rem);
213
+ padding: var(--space-2, 0.5rem) 0;
214
+ }
215
+
216
+ .inv-card__shimmer {
217
+ height: 16px;
218
+ width: 100%;
219
+ border-radius: 4px;
220
+ background: linear-gradient(
221
+ 90deg,
222
+ var(--admin-border) 25%,
223
+ var(--admin-content-bg) 50%,
224
+ var(--admin-border) 75%
225
+ );
226
+ background-size: 200% 100%;
227
+ animation: inv-shimmer 1.5s ease-in-out infinite;
228
+ }
229
+
230
+ .inv-card__shimmer--short {
231
+ width: 60%;
232
+ }
233
+
234
+ .inv-card__shimmer--medium {
235
+ width: 80%;
236
+ }
237
+
238
+ @keyframes inv-shimmer {
239
+ 0% { background-position: 200% 0; }
240
+ 100% { background-position: -200% 0; }
241
+ }
242
+
243
+ .sr-only {
244
+ position: absolute;
245
+ width: 1px;
246
+ height: 1px;
247
+ padding: 0;
248
+ margin: -1px;
249
+ overflow: hidden;
250
+ clip: rect(0, 0, 0, 0);
251
+ white-space: nowrap;
252
+ border-width: 0;
253
+ }
254
+ </style>
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <div class="inv-empty-state" role="status">
3
+ <svg
4
+ class="inv-empty-state__icon"
5
+ width="48"
6
+ height="48"
7
+ viewBox="0 0 48 48"
8
+ fill="none"
9
+ aria-hidden="true"
10
+ >
11
+ <rect
12
+ x="6"
13
+ y="10"
14
+ width="36"
15
+ height="28"
16
+ rx="4"
17
+ stroke="currentColor"
18
+ stroke-width="2"
19
+ fill="none"
20
+ />
21
+ <path
22
+ d="M6 18H42"
23
+ stroke="currentColor"
24
+ stroke-width="2"
25
+ />
26
+ <circle cx="12" cy="14" r="1.5" fill="currentColor" />
27
+ <circle cx="17" cy="14" r="1.5" fill="currentColor" />
28
+ <circle cx="22" cy="14" r="1.5" fill="currentColor" />
29
+ <path
30
+ d="M18 28L22 24L26 28L30 22"
31
+ stroke="currentColor"
32
+ stroke-width="2"
33
+ stroke-linecap="round"
34
+ stroke-linejoin="round"
35
+ opacity="0.4"
36
+ />
37
+ </svg>
38
+
39
+ <h3 class="inv-empty-state__title">{{ title }}</h3>
40
+
41
+ <p v-if="description" class="inv-empty-state__description">
42
+ {{ description }}
43
+ </p>
44
+
45
+ <div v-if="$slots.action || actionLabel" class="inv-empty-state__action">
46
+ <slot name="action">
47
+ <button
48
+ v-if="actionLabel"
49
+ type="button"
50
+ class="inv-empty-state__btn"
51
+ @click="emit('action')"
52
+ >
53
+ {{ actionLabel }}
54
+ </button>
55
+ </slot>
56
+ </div>
57
+ </div>
58
+ </template>
59
+
60
+ <script setup lang="ts">
61
+ defineProps<{
62
+ title: string
63
+ description?: string
64
+ actionLabel?: string
65
+ }>()
66
+
67
+ const emit = defineEmits<{
68
+ action: []
69
+ }>()
70
+ </script>
71
+
72
+ <style scoped>
73
+ .inv-empty-state {
74
+ display: flex;
75
+ flex-direction: column;
76
+ align-items: center;
77
+ justify-content: center;
78
+ text-align: center;
79
+ padding: var(--space-8, 2rem) var(--space-4, 1rem);
80
+ }
81
+
82
+ .inv-empty-state__icon {
83
+ color: var(--admin-text-tertiary);
84
+ margin-bottom: var(--space-4, 1rem);
85
+ opacity: 0.6;
86
+ }
87
+
88
+ .inv-empty-state__title {
89
+ margin: 0;
90
+ font-size: var(--text-base, 1rem);
91
+ font-weight: 600;
92
+ color: var(--admin-text-secondary);
93
+ line-height: 1.4;
94
+ }
95
+
96
+ .inv-empty-state__description {
97
+ margin: var(--space-2, 0.5rem) 0 0;
98
+ font-size: var(--text-sm, 0.875rem);
99
+ color: var(--admin-text-tertiary);
100
+ line-height: 1.5;
101
+ max-width: 360px;
102
+ }
103
+
104
+ .inv-empty-state__action {
105
+ margin-top: var(--space-5, 1.25rem);
106
+ }
107
+
108
+ .inv-empty-state__btn {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ min-height: 44px;
113
+ padding: var(--space-2, 0.5rem) var(--space-5, 1.25rem);
114
+ border: none;
115
+ border-radius: 6px;
116
+ background: var(--color-primary, #2563eb);
117
+ color: #fff;
118
+ font-size: var(--text-sm, 0.875rem);
119
+ font-weight: 500;
120
+ cursor: pointer;
121
+ transition: background-color 0.15s ease;
122
+ }
123
+
124
+ .inv-empty-state__btn:hover {
125
+ background: color-mix(in srgb, var(--color-primary, #2563eb) 85%, black);
126
+ }
127
+
128
+ .inv-empty-state__btn:focus-visible {
129
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
130
+ outline-offset: 2px;
131
+ }
132
+ </style>