@softdin/shared 0.1.3

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 (40) hide show
  1. package/README.md +48 -0
  2. package/package.json +50 -0
  3. package/src/a11y/announcer.ts +43 -0
  4. package/src/a11y/apply.ts +83 -0
  5. package/src/a11y/index.ts +31 -0
  6. package/src/a11y/state.ts +40 -0
  7. package/src/a11y/storage.ts +106 -0
  8. package/src/a11y/types.ts +187 -0
  9. package/src/api.ts +89 -0
  10. package/src/assets/softdin-logo.png +0 -0
  11. package/src/auth.ts +134 -0
  12. package/src/components/a11y/A11yLiveRegion.vue +11 -0
  13. package/src/components/a11y/AccessibilityMenuControls.vue +426 -0
  14. package/src/components/a11y/AccessibilityPreferencesPanel.vue +331 -0
  15. package/src/components/a11y/ColorSchemeQuickToggle.vue +105 -0
  16. package/src/components/a11y/SkipLink.vue +41 -0
  17. package/src/components/auth/AuthBrandHeader.vue +129 -0
  18. package/src/components/auth/AuthShell.vue +52 -0
  19. package/src/components/auth/DataProcessingConsentCheckbox.vue +303 -0
  20. package/src/components/auth/DataProcessingConsentForm.vue +111 -0
  21. package/src/components/forms/ErrorAlert.vue +170 -0
  22. package/src/components/forms/FormCancelButton.vue +87 -0
  23. package/src/components/forms/FormCloseButton.vue +114 -0
  24. package/src/components/forms/FormHelpButton.vue +109 -0
  25. package/src/components/forms/FormHelpModal.vue +193 -0
  26. package/src/components/forms/FormMaximizeButton.vue +143 -0
  27. package/src/components/forms/FormRefreshButton.vue +152 -0
  28. package/src/components/forms/FormSaveButton.vue +113 -0
  29. package/src/components/forms/PasswordInput.vue +154 -0
  30. package/src/components/navigation/CatalogListPagination.vue +113 -0
  31. package/src/config/apiBase.ts +37 -0
  32. package/src/lib/catalog-pagination.ts +27 -0
  33. package/src/lib/pagination.ts +86 -0
  34. package/src/lib/softdin-libreria.ts +50 -0
  35. package/src/style.css +278 -0
  36. package/src/types/auth.ts +146 -0
  37. package/src/types/softdin-libreria.d.ts +41 -0
  38. package/src/utils/apiError.ts +55 -0
  39. package/src/utils/connectivity.ts +33 -0
  40. package/src/utils/tenantLogo.ts +6 -0
@@ -0,0 +1,114 @@
1
+ <script setup lang="ts">
2
+ withDefaults(
3
+ defineProps<{
4
+ disabled?: boolean
5
+ label?: string
6
+ }>(),
7
+ {
8
+ disabled: false,
9
+ label: 'Cerrar',
10
+ },
11
+ )
12
+
13
+ defineEmits<{
14
+ click: [event: MouseEvent]
15
+ }>()
16
+ </script>
17
+
18
+ <template>
19
+ <button
20
+ type="button"
21
+ class="softdin-form-close"
22
+ :aria-label="label"
23
+ :title="label"
24
+ :disabled="disabled"
25
+ @click="$emit('click', $event)"
26
+ >
27
+ <svg class="softdin-form-close__icon" viewBox="0 0 20 20" aria-hidden="true">
28
+ <circle cx="10" cy="10" r="8" />
29
+ <path d="M6.5 6.5l7 7M13.5 6.5l-7 7" />
30
+ </svg>
31
+ <span>{{ label }}</span>
32
+ </button>
33
+ </template>
34
+
35
+ <style scoped>
36
+ .softdin-form-close {
37
+ position: relative;
38
+ display: inline-flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ width: 2.25rem;
42
+ height: 2.25rem;
43
+ border: 1px solid #fecaca;
44
+ border-radius: 9999px;
45
+ background: #fef2f2;
46
+ padding: 0;
47
+ color: #b91c1c;
48
+ cursor: pointer;
49
+ transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
50
+ }
51
+
52
+ .softdin-form-close:hover:not(:disabled),
53
+ .softdin-form-close:focus-visible {
54
+ background: #fee2e2;
55
+ box-shadow: 0 6px 14px rgba(185, 28, 28, 0.2);
56
+ transform: translateY(-2px) rotate(4deg);
57
+ }
58
+
59
+ .softdin-form-close:focus-visible {
60
+ outline: 2px solid #dc2626;
61
+ outline-offset: 2px;
62
+ }
63
+
64
+ .softdin-form-close:disabled {
65
+ opacity: 0.55;
66
+ cursor: not-allowed;
67
+ }
68
+
69
+ .softdin-form-close span {
70
+ position: absolute;
71
+ top: 50%;
72
+ right: calc(100% + 0.5rem);
73
+ z-index: 2;
74
+ transform: translateY(-50%) translateX(0.25rem);
75
+ border-radius: 0.375rem;
76
+ background: #0f172a;
77
+ padding: 0.25rem 0.5rem;
78
+ font-size: 0.75rem;
79
+ font-weight: 600;
80
+ white-space: nowrap;
81
+ color: #fff;
82
+ opacity: 0;
83
+ pointer-events: none;
84
+ transition: opacity 150ms ease, transform 150ms ease;
85
+ }
86
+
87
+ .softdin-form-close:hover:not(:disabled) span,
88
+ .softdin-form-close:focus-visible span {
89
+ transform: translateY(-50%) translateX(0) rotate(-4deg);
90
+ opacity: 1;
91
+ }
92
+
93
+ .softdin-form-close__icon {
94
+ width: 1rem;
95
+ height: 1rem;
96
+ fill: none;
97
+ stroke: currentColor;
98
+ stroke-width: 1.8;
99
+ stroke-linecap: round;
100
+ stroke-linejoin: round;
101
+ }
102
+
103
+ @media (prefers-reduced-motion: reduce) {
104
+ .softdin-form-close,
105
+ .softdin-form-close span {
106
+ transition: none;
107
+ }
108
+
109
+ .softdin-form-close:hover:not(:disabled),
110
+ .softdin-form-close:focus-visible {
111
+ transform: none;
112
+ }
113
+ }
114
+ </style>
@@ -0,0 +1,109 @@
1
+ <script setup lang="ts">
2
+ withDefaults(
3
+ defineProps<{
4
+ label?: string
5
+ title?: string
6
+ }>(),
7
+ {
8
+ label: 'Ayuda',
9
+ title: 'Ayuda del formulario',
10
+ },
11
+ )
12
+
13
+ defineEmits<{
14
+ click: [event: MouseEvent]
15
+ }>()
16
+ </script>
17
+
18
+ <template>
19
+ <button
20
+ type="button"
21
+ class="softdin-form-help"
22
+ :aria-label="title"
23
+ :title="title"
24
+ @click="$emit('click', $event)"
25
+ >
26
+ <svg class="softdin-form-help__icon" viewBox="0 0 20 20" aria-hidden="true">
27
+ <circle cx="10" cy="10" r="8" />
28
+ <path d="M7.8 7.5a2.35 2.35 0 0 1 4.55.8c0 1.65-1.72 2.05-2.1 3.2" />
29
+ <path d="M10 14.15h.01" />
30
+ </svg>
31
+ <span>{{ label }}</span>
32
+ </button>
33
+ </template>
34
+
35
+ <style scoped>
36
+ .softdin-form-help {
37
+ position: relative;
38
+ display: inline-flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ width: 2.25rem;
42
+ height: 2.25rem;
43
+ border: 1px solid var(--color-brand-100, #bbf7d0);
44
+ border-radius: 9999px;
45
+ background: var(--color-brand-50, #f0fdf4);
46
+ padding: 0;
47
+ color: var(--color-brand-700);
48
+ cursor: pointer;
49
+ transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
50
+ }
51
+
52
+ .softdin-form-help:hover,
53
+ .softdin-form-help:focus-visible {
54
+ background: var(--color-brand-100, #dcfce7);
55
+ box-shadow: 0 6px 14px color-mix(in srgb, var(--color-brand-700) 20%, transparent);
56
+ transform: translateY(-2px) rotate(4deg);
57
+ }
58
+
59
+ .softdin-form-help:focus-visible {
60
+ outline: 2px solid var(--color-brand-600);
61
+ outline-offset: 2px;
62
+ }
63
+
64
+ .softdin-form-help span {
65
+ position: absolute;
66
+ top: 50%;
67
+ right: calc(100% + 0.5rem);
68
+ z-index: 2;
69
+ transform: translateY(-50%) translateX(0.25rem);
70
+ border-radius: 0.375rem;
71
+ background: #0f172a;
72
+ padding: 0.25rem 0.5rem;
73
+ font-size: 0.75rem;
74
+ font-weight: 600;
75
+ white-space: nowrap;
76
+ color: #fff;
77
+ opacity: 0;
78
+ pointer-events: none;
79
+ transition: opacity 150ms ease, transform 150ms ease;
80
+ }
81
+
82
+ .softdin-form-help:hover span,
83
+ .softdin-form-help:focus-visible span {
84
+ transform: translateY(-50%) translateX(0) rotate(-4deg);
85
+ opacity: 1;
86
+ }
87
+
88
+ .softdin-form-help__icon {
89
+ width: 1rem;
90
+ height: 1rem;
91
+ fill: none;
92
+ stroke: currentColor;
93
+ stroke-width: 1.8;
94
+ stroke-linecap: round;
95
+ stroke-linejoin: round;
96
+ }
97
+
98
+ @media (prefers-reduced-motion: reduce) {
99
+ .softdin-form-help,
100
+ .softdin-form-help span {
101
+ transition: none;
102
+ }
103
+
104
+ .softdin-form-help:hover,
105
+ .softdin-form-help:focus-visible {
106
+ transform: none;
107
+ }
108
+ }
109
+ </style>
@@ -0,0 +1,193 @@
1
+ <script setup lang="ts">
2
+ import FormCloseButton from './FormCloseButton.vue'
3
+
4
+ export type FormHelpStep = string | { title: string; body: string }
5
+
6
+ const open = defineModel<boolean>('open', { default: false })
7
+
8
+ withDefaults(
9
+ defineProps<{
10
+ title: string
11
+ kicker?: string
12
+ intro?: string
13
+ steps?: FormHelpStep[]
14
+ note?: string
15
+ fieldNote?: string
16
+ confirmLabel?: string
17
+ }>(),
18
+ {
19
+ kicker: 'Guía funcional',
20
+ confirmLabel: 'Entendido',
21
+ },
22
+ )
23
+
24
+ function close(): void {
25
+ open.value = false
26
+ }
27
+
28
+ function stepTitle(step: FormHelpStep): string | null {
29
+ return typeof step === 'string' ? null : step.title
30
+ }
31
+
32
+ function stepBody(step: FormHelpStep): string {
33
+ return typeof step === 'string' ? step : step.body
34
+ }
35
+ </script>
36
+
37
+ <template>
38
+ <Teleport to="body">
39
+ <div v-if="open" class="softdin-help-backdrop" @click.self="close">
40
+ <section
41
+ class="softdin-help-panel"
42
+ role="dialog"
43
+ aria-modal="true"
44
+ :aria-labelledby="`softdin-help-title-${title}`"
45
+ >
46
+ <header class="softdin-help-header">
47
+ <div>
48
+ <p class="softdin-help-kicker">{{ kicker }}</p>
49
+ <h2 :id="`softdin-help-title-${title}`" class="softdin-help-title">{{ title }}</h2>
50
+ </div>
51
+ <FormCloseButton label="Cerrar" @click="close" />
52
+ </header>
53
+
54
+ <div class="softdin-help-body">
55
+ <slot>
56
+ <p v-if="intro">{{ intro }}</p>
57
+
58
+ <ol v-if="steps?.length" class="softdin-help-steps">
59
+ <li v-for="(step, index) in steps" :key="index">
60
+ <template v-if="stepTitle(step)">
61
+ <strong>{{ stepTitle(step) }}</strong>
62
+ {{ stepBody(step) }}
63
+ </template>
64
+ <template v-else>
65
+ {{ stepBody(step) }}
66
+ </template>
67
+ </li>
68
+ </ol>
69
+
70
+ <aside v-if="note" class="softdin-help-note">
71
+ <strong>Importante:</strong> {{ note }}
72
+ </aside>
73
+
74
+ <p v-if="fieldNote" class="softdin-help-field-note">{{ fieldNote }}</p>
75
+ </slot>
76
+ </div>
77
+
78
+ <footer class="softdin-help-footer">
79
+ <button type="button" class="softdin-help-confirm" @click="close">
80
+ {{ confirmLabel }}
81
+ </button>
82
+ </footer>
83
+ </section>
84
+ </div>
85
+ </Teleport>
86
+ </template>
87
+
88
+ <style scoped>
89
+ .softdin-help-backdrop {
90
+ position: fixed;
91
+ inset: 0;
92
+ z-index: 130;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ padding: 1rem;
97
+ background: rgba(15, 23, 42, 0.55);
98
+ }
99
+
100
+ .softdin-help-panel {
101
+ width: 100%;
102
+ max-width: 42rem;
103
+ max-height: calc(100vh - 2rem);
104
+ display: flex;
105
+ flex-direction: column;
106
+ overflow: hidden;
107
+ border-radius: 1rem;
108
+ background: var(--color-panel, #fff);
109
+ box-shadow: 0 20px 50px rgba(15, 23, 42, 0.3);
110
+ }
111
+
112
+ .softdin-help-header,
113
+ .softdin-help-footer {
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: space-between;
117
+ gap: 1rem;
118
+ padding: 1rem 1.25rem;
119
+ }
120
+
121
+ .softdin-help-header {
122
+ border-bottom: 1px solid var(--color-border, #e2e8f0);
123
+ }
124
+
125
+ .softdin-help-footer {
126
+ border-top: 1px solid var(--color-border, #e2e8f0);
127
+ justify-content: flex-end;
128
+ }
129
+
130
+ .softdin-help-kicker {
131
+ margin: 0;
132
+ font-size: 0.75rem;
133
+ font-weight: 600;
134
+ letter-spacing: 0.05em;
135
+ text-transform: uppercase;
136
+ color: var(--color-brand-700);
137
+ }
138
+
139
+ .softdin-help-title {
140
+ margin: 0.25rem 0 0;
141
+ font-size: 1.125rem;
142
+ color: var(--color-text, #0f172a);
143
+ }
144
+
145
+ .softdin-help-body {
146
+ flex: 1;
147
+ min-height: 0;
148
+ overflow-y: auto;
149
+ padding: 1rem 1.25rem;
150
+ font-size: 0.9rem;
151
+ line-height: 1.55;
152
+ color: var(--color-text-muted, #334155);
153
+ }
154
+
155
+ .softdin-help-body p {
156
+ margin: 0;
157
+ }
158
+
159
+ .softdin-help-steps {
160
+ display: grid;
161
+ gap: 0.75rem;
162
+ margin: 1rem 0;
163
+ padding-left: 1.25rem;
164
+ }
165
+
166
+ .softdin-help-note {
167
+ border-radius: 0.625rem;
168
+ background: #fff7ed;
169
+ padding: 0.75rem;
170
+ color: #9a3412;
171
+ }
172
+
173
+ .softdin-help-field-note {
174
+ margin-top: 1rem !important;
175
+ font-size: 0.8125rem;
176
+ color: var(--color-text-muted, #64748b);
177
+ }
178
+
179
+ .softdin-help-confirm {
180
+ border: 0;
181
+ border-radius: 0.5rem;
182
+ background: var(--color-brand-600);
183
+ padding: 0.5rem 0.875rem;
184
+ font-size: 0.875rem;
185
+ font-weight: 500;
186
+ color: #fff;
187
+ cursor: pointer;
188
+ }
189
+
190
+ .softdin-help-confirm:hover {
191
+ background: var(--color-brand-700);
192
+ }
193
+ </style>
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ withDefaults(
3
+ defineProps<{
4
+ maximized?: boolean
5
+ disabled?: boolean
6
+ maximizeLabel?: string
7
+ restoreLabel?: string
8
+ }>(),
9
+ {
10
+ maximized: false,
11
+ disabled: false,
12
+ maximizeLabel: 'Maximizar',
13
+ restoreLabel: 'Restaurar',
14
+ },
15
+ )
16
+
17
+ defineEmits<{
18
+ click: [event: MouseEvent]
19
+ }>()
20
+ </script>
21
+
22
+ <template>
23
+ <button
24
+ type="button"
25
+ class="softdin-form-maximize"
26
+ :aria-label="maximized ? restoreLabel : maximizeLabel"
27
+ :title="maximized ? restoreLabel : maximizeLabel"
28
+ :aria-pressed="maximized"
29
+ :disabled="disabled"
30
+ @click="$emit('click', $event)"
31
+ >
32
+ <!-- Restaurar: flechas hacia adentro (como en Perfil de Cargo) -->
33
+ <svg
34
+ v-if="maximized"
35
+ class="softdin-form-maximize__icon"
36
+ viewBox="0 0 20 20"
37
+ aria-hidden="true"
38
+ >
39
+ <path d="M7.5 3.5v4h-4" />
40
+ <path d="M12.5 3.5v4h4" />
41
+ <path d="M7.5 16.5v-4h-4" />
42
+ <path d="M12.5 16.5v-4h4" />
43
+ <path d="M7.5 7.5 3.5 3.5" />
44
+ <path d="M12.5 7.5l4-4" />
45
+ <path d="M7.5 12.5l-4 4" />
46
+ <path d="M12.5 12.5l4 4" />
47
+ </svg>
48
+ <!-- Maximizar: flechas hacia las esquinas -->
49
+ <svg v-else class="softdin-form-maximize__icon" viewBox="0 0 20 20" aria-hidden="true">
50
+ <path d="M3.5 7.5v-4h4" />
51
+ <path d="M16.5 7.5v-4h-4" />
52
+ <path d="M3.5 12.5v4h4" />
53
+ <path d="M16.5 12.5v4h-4" />
54
+ <path d="M3.5 3.5 7.5 7.5" />
55
+ <path d="M16.5 3.5 12.5 7.5" />
56
+ <path d="M3.5 16.5 7.5 12.5" />
57
+ <path d="M16.5 16.5 12.5 12.5" />
58
+ </svg>
59
+ <span>{{ maximized ? restoreLabel : maximizeLabel }}</span>
60
+ </button>
61
+ </template>
62
+
63
+ <style scoped>
64
+ .softdin-form-maximize {
65
+ position: relative;
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 2.25rem;
70
+ height: 2.25rem;
71
+ border: 1px solid #cbd5e1;
72
+ border-radius: 9999px;
73
+ background: #f8fafc;
74
+ padding: 0;
75
+ color: #475569;
76
+ cursor: pointer;
77
+ transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease, color 180ms ease;
78
+ }
79
+
80
+ .softdin-form-maximize:hover:not(:disabled),
81
+ .softdin-form-maximize:focus-visible {
82
+ background: #f1f5f9;
83
+ color: #0f172a;
84
+ box-shadow: 0 6px 14px rgba(15, 23, 42, 0.12);
85
+ transform: translateY(-2px) rotate(4deg);
86
+ }
87
+
88
+ .softdin-form-maximize:focus-visible {
89
+ outline: 2px solid #64748b;
90
+ outline-offset: 2px;
91
+ }
92
+
93
+ .softdin-form-maximize:disabled {
94
+ opacity: 0.55;
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ .softdin-form-maximize span {
99
+ position: absolute;
100
+ top: 50%;
101
+ right: calc(100% + 0.5rem);
102
+ z-index: 2;
103
+ transform: translateY(-50%) translateX(0.25rem);
104
+ border-radius: 0.375rem;
105
+ background: #0f172a;
106
+ padding: 0.25rem 0.5rem;
107
+ font-size: 0.75rem;
108
+ font-weight: 600;
109
+ white-space: nowrap;
110
+ color: #fff;
111
+ opacity: 0;
112
+ pointer-events: none;
113
+ transition: opacity 150ms ease, transform 150ms ease;
114
+ }
115
+
116
+ .softdin-form-maximize:hover:not(:disabled) span,
117
+ .softdin-form-maximize:focus-visible span {
118
+ transform: translateY(-50%) translateX(0) rotate(-4deg);
119
+ opacity: 1;
120
+ }
121
+
122
+ .softdin-form-maximize__icon {
123
+ width: 1rem;
124
+ height: 1rem;
125
+ fill: none;
126
+ stroke: currentColor;
127
+ stroke-width: 1.8;
128
+ stroke-linecap: round;
129
+ stroke-linejoin: round;
130
+ }
131
+
132
+ @media (prefers-reduced-motion: reduce) {
133
+ .softdin-form-maximize,
134
+ .softdin-form-maximize span {
135
+ transition: none;
136
+ }
137
+
138
+ .softdin-form-maximize:hover:not(:disabled),
139
+ .softdin-form-maximize:focus-visible {
140
+ transform: none;
141
+ }
142
+ }
143
+ </style>
@@ -0,0 +1,152 @@
1
+ <script setup lang="ts">
2
+ withDefaults(
3
+ defineProps<{
4
+ loading?: boolean
5
+ disabled?: boolean
6
+ label?: string
7
+ loadingLabel?: string
8
+ /** Botón circular solo-ícono (sin texto visible), con tooltip al pasar el mouse. */
9
+ iconOnly?: boolean
10
+ }>(),
11
+ {
12
+ loading: false,
13
+ disabled: false,
14
+ label: 'Actualizar',
15
+ loadingLabel: 'Actualizando…',
16
+ iconOnly: false,
17
+ },
18
+ )
19
+
20
+ defineEmits<{
21
+ click: [event: MouseEvent]
22
+ }>()
23
+ </script>
24
+
25
+ <template>
26
+ <button
27
+ type="button"
28
+ class="softdin-form-refresh"
29
+ :class="{ 'softdin-form-refresh--icon-only': iconOnly }"
30
+ :aria-label="loading ? loadingLabel : label"
31
+ :title="loading ? loadingLabel : label"
32
+ :data-tooltip="iconOnly ? (loading ? loadingLabel : label) : null"
33
+ :disabled="disabled || loading"
34
+ @click="$emit('click', $event)"
35
+ >
36
+ <svg
37
+ class="softdin-form-refresh__icon"
38
+ :class="{ 'softdin-form-refresh__icon--loading': loading }"
39
+ viewBox="0 0 20 20"
40
+ aria-hidden="true"
41
+ >
42
+ <path d="M16.5 10a6.5 6.5 0 1 1-1.9-4.6" />
43
+ <path d="M16.5 3.5v4h-4" />
44
+ </svg>
45
+ <span v-if="!iconOnly">{{ loading ? loadingLabel : label }}</span>
46
+ </button>
47
+ </template>
48
+
49
+ <style scoped>
50
+ .softdin-form-refresh {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ gap: 0.5rem;
55
+ min-height: 2.25rem;
56
+ border: 0;
57
+ border-radius: 9999px;
58
+ background: var(--color-brand-600);
59
+ padding: 0.5rem 1rem;
60
+ font-size: 0.875rem;
61
+ font-weight: 600;
62
+ color: #fff;
63
+ cursor: pointer;
64
+ transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
65
+ }
66
+
67
+ .softdin-form-refresh:hover:not(:disabled),
68
+ .softdin-form-refresh:focus-visible {
69
+ background: var(--color-brand-700);
70
+ box-shadow: 0 6px 14px color-mix(in srgb, var(--color-brand-700) 28%, transparent);
71
+ transform: translateY(-2px);
72
+ }
73
+
74
+ .softdin-form-refresh:focus-visible {
75
+ outline: 2px solid var(--color-brand-600);
76
+ outline-offset: 2px;
77
+ }
78
+
79
+ .softdin-form-refresh:disabled {
80
+ opacity: 0.55;
81
+ cursor: not-allowed;
82
+ }
83
+
84
+ .softdin-form-refresh--icon-only {
85
+ position: relative;
86
+ gap: 0;
87
+ height: 2.25rem;
88
+ width: 2.25rem;
89
+ min-height: 0;
90
+ padding: 0;
91
+ }
92
+
93
+ .softdin-form-refresh--icon-only[data-tooltip]::after {
94
+ content: attr(data-tooltip);
95
+ position: absolute;
96
+ bottom: calc(100% + 0.375rem);
97
+ left: 50%;
98
+ z-index: 10;
99
+ transform: translateX(-50%);
100
+ border-radius: 0.375rem;
101
+ background: #0f172a;
102
+ padding: 0.25rem 0.5rem;
103
+ font-size: 0.6875rem;
104
+ font-weight: 500;
105
+ white-space: nowrap;
106
+ color: #fff;
107
+ opacity: 0;
108
+ pointer-events: none;
109
+ transition: opacity 150ms ease;
110
+ }
111
+
112
+ .softdin-form-refresh--icon-only[data-tooltip]:hover::after,
113
+ .softdin-form-refresh--icon-only[data-tooltip]:focus-visible::after {
114
+ opacity: 1;
115
+ }
116
+
117
+ .softdin-form-refresh__icon {
118
+ width: 1rem;
119
+ height: 1rem;
120
+ flex-shrink: 0;
121
+ fill: none;
122
+ stroke: currentColor;
123
+ stroke-width: 1.8;
124
+ stroke-linecap: round;
125
+ stroke-linejoin: round;
126
+ }
127
+
128
+ .softdin-form-refresh__icon--loading {
129
+ animation: softdin-form-refresh-spin 1s linear infinite;
130
+ }
131
+
132
+ @keyframes softdin-form-refresh-spin {
133
+ to {
134
+ transform: rotate(360deg);
135
+ }
136
+ }
137
+
138
+ @media (prefers-reduced-motion: reduce) {
139
+ .softdin-form-refresh {
140
+ transition: none;
141
+ }
142
+
143
+ .softdin-form-refresh:hover:not(:disabled),
144
+ .softdin-form-refresh:focus-visible {
145
+ transform: none;
146
+ }
147
+
148
+ .softdin-form-refresh__icon--loading {
149
+ animation: none;
150
+ }
151
+ }
152
+ </style>