@klodd/ds 1.2.0 → 3.0.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.
@@ -0,0 +1,200 @@
1
+ /* ================================================================
2
+ components/swipe-stack.css
3
+ Tinder-stil swipe-card-stack med drag-physics. Anvands for
4
+ triage-flow, decision-cards, tutorial-stacks.
5
+
6
+ Klassnamn ar generiska (.swipe-*) - inte vy-specifika (.triage-*).
7
+ App-repots vyer applicerar dessa pa sina specifika data-cards.
8
+
9
+ Element:
10
+ - .swipe-stack: container med relative position
11
+ - .swipe-card: individuellt kort (absolute positionerat)
12
+ - .swipe-card__header: ovre header-omrade
13
+ - .swipe-card__body: huvud-innehall
14
+ - .swipe-card__footer: nedre actions-rad
15
+ - .swipe-card__back: nasta kort i stacken (visuellt bakom)
16
+ - .swipe-decision-overlay: overlay med "spara"/"avfard" indikator
17
+ - .swipe-actions: knapp-rad under stacken
18
+ - .swipe-meta: kort-meta-info
19
+ - .swipe-progress: progress-indikator
20
+ - .swipe-empty: tomt-state
21
+ ================================================================ */
22
+ .swipe-stack {
23
+ position: relative;
24
+ width: 100%;
25
+ max-width: 480px;
26
+ margin: 0 auto;
27
+ height: 480px;
28
+ isolation: isolate;
29
+ }
30
+
31
+ .swipe-card {
32
+ position: absolute;
33
+ inset: 0;
34
+ display: flex;
35
+ flex-direction: column;
36
+ background: var(--surface-raised);
37
+ border: 1px solid var(--border-subtle);
38
+ border-radius: var(--radius-20);
39
+ padding: var(--space-20);
40
+ overflow: hidden;
41
+ cursor: grab;
42
+ user-select: none;
43
+ -webkit-user-select: none;
44
+ touch-action: pan-y;
45
+ transition: transform var(--dur-medium) var(--ease-spring-snappy);
46
+ }
47
+
48
+ .swipe-card:active { cursor: grabbing; }
49
+
50
+ .swipe-card--back {
51
+ transform: scale(0.96);
52
+ opacity: 0.85;
53
+ z-index: 0;
54
+ pointer-events: none;
55
+ }
56
+
57
+ .swipe-card__header {
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: space-between;
61
+ gap: var(--space-12);
62
+ margin-bottom: var(--space-12);
63
+ }
64
+
65
+ .swipe-card__title {
66
+ font-size: var(--fs-17);
67
+ font-weight: var(--fw-medium);
68
+ color: var(--text-default);
69
+ letter-spacing: var(--ls-tight);
70
+ margin: 0 0 var(--space-6);
71
+ }
72
+
73
+ .swipe-card__body {
74
+ flex: 1;
75
+ overflow-y: auto;
76
+ -webkit-overflow-scrolling: touch;
77
+ font-size: var(--fs-14);
78
+ color: var(--text-subtle);
79
+ line-height: var(--lh-base);
80
+ }
81
+
82
+ .swipe-card__footer {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: space-between;
86
+ gap: var(--space-12);
87
+ margin-top: var(--space-16);
88
+ padding-top: var(--space-12);
89
+ border-top: 1px solid var(--border-subtle);
90
+ font-size: var(--fs-12);
91
+ color: var(--text-muted);
92
+ }
93
+
94
+ .swipe-decision-overlay {
95
+ position: absolute;
96
+ top: var(--space-20);
97
+ padding: var(--space-8) var(--space-16);
98
+ font-size: var(--fs-15);
99
+ font-weight: var(--fw-medium);
100
+ letter-spacing: 0.05em;
101
+ text-transform: uppercase;
102
+ border-radius: var(--radius-10);
103
+ border: 2px solid currentColor;
104
+ opacity: 0;
105
+ transition: opacity var(--dur-fast) var(--ease-default);
106
+ pointer-events: none;
107
+ }
108
+
109
+ .swipe-decision-overlay--save {
110
+ right: var(--space-20);
111
+ color: var(--positive);
112
+ transform: rotate(8deg);
113
+ }
114
+
115
+ .swipe-decision-overlay--dismiss {
116
+ left: var(--space-20);
117
+ color: var(--negative);
118
+ transform: rotate(-8deg);
119
+ }
120
+
121
+ .swipe-decision-overlay--visible { opacity: 1; }
122
+
123
+ .swipe-actions {
124
+ display: flex;
125
+ justify-content: center;
126
+ gap: var(--space-20);
127
+ margin-top: var(--space-20);
128
+ }
129
+
130
+ .swipe-action-btn {
131
+ display: inline-flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ width: 56px;
135
+ height: 56px;
136
+ background: var(--surface-raised);
137
+ border: 1px solid var(--border-default);
138
+ border-radius: var(--radius-full);
139
+ color: var(--text-default);
140
+ cursor: pointer;
141
+ -webkit-tap-highlight-color: transparent;
142
+ touch-action: manipulation;
143
+ transition:
144
+ background var(--dur-fast) var(--ease-spring-snappy),
145
+ transform var(--press-out-duration) var(--press-out-easing);
146
+ }
147
+
148
+ @media (hover: hover) and (pointer: fine) {
149
+ .swipe-action-btn:hover { background: var(--surface-hover); }
150
+ }
151
+
152
+ .swipe-action-btn:active {
153
+ transform: scale(0.92);
154
+ transition: transform var(--press-in-duration) var(--press-in-easing);
155
+ }
156
+
157
+ .swipe-action-btn--save { color: var(--positive); }
158
+ .swipe-action-btn--dismiss { color: var(--negative); }
159
+
160
+ .swipe-meta {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: var(--space-8);
164
+ font-size: var(--fs-12);
165
+ color: var(--text-muted);
166
+ }
167
+
168
+ .swipe-meta-sep {
169
+ display: inline-block;
170
+ width: 3px;
171
+ height: 3px;
172
+ background: var(--text-disabled);
173
+ border-radius: var(--radius-full);
174
+ }
175
+
176
+ .swipe-progress {
177
+ text-align: center;
178
+ font-size: var(--fs-12);
179
+ color: var(--text-muted);
180
+ margin: 0 0 var(--space-12);
181
+ font-variant-numeric: tabular-nums;
182
+ }
183
+
184
+ .swipe-empty {
185
+ text-align: center;
186
+ padding: var(--space-40) var(--space-20);
187
+ color: var(--text-subtle);
188
+ }
189
+
190
+ .swipe-hint {
191
+ text-align: center;
192
+ font-size: var(--fs-12);
193
+ color: var(--text-disabled);
194
+ margin-top: var(--space-12);
195
+ }
196
+
197
+ @media (prefers-reduced-motion: reduce) {
198
+ .swipe-card { transition: none; }
199
+ .swipe-decision-overlay { transition: none; }
200
+ }
@@ -0,0 +1,58 @@
1
+ /* ================================================================
2
+ components/tab-bar.css
3
+ Inline tab-grupp for format-val (CSV/Avi, dashboard-vyer m.fl.).
4
+ Inte sticky - placeras inline i template.
5
+
6
+ Existerande nav.css har en kompaktare .tab-bar-variant.
7
+ Denna fil definierar OM och ersatter den - lasta en gang.
8
+ ================================================================ */
9
+ .tab-bar {
10
+ display: flex;
11
+ gap: var(--space-4);
12
+ margin: 0 0 var(--space-16);
13
+ padding: var(--space-4);
14
+ background: var(--surface-default);
15
+ border-radius: var(--radius-12);
16
+ }
17
+
18
+ .tab-bar__item {
19
+ flex: 1;
20
+ display: inline-flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ min-height: 44px;
24
+ padding: 0 var(--space-12);
25
+ background: transparent;
26
+ border: 0;
27
+ border-radius: var(--radius-8);
28
+ color: var(--text-subtle);
29
+ font-family: inherit;
30
+ font-size: var(--fs-14);
31
+ font-weight: var(--fw-medium);
32
+ text-decoration: none;
33
+ cursor: pointer;
34
+ -webkit-tap-highlight-color: transparent;
35
+ touch-action: manipulation;
36
+ transition:
37
+ background var(--dur-fast) var(--ease-spring-snappy),
38
+ color var(--dur-fast) var(--ease-spring-snappy);
39
+ }
40
+
41
+ .tab-bar__item.is-active {
42
+ background: var(--surface-raised);
43
+ color: var(--text-default);
44
+ font-weight: var(--fw-medium);
45
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
46
+ }
47
+
48
+ @media (hover: hover) and (pointer: fine) {
49
+ .tab-bar__item:not(.is-active):hover {
50
+ color: var(--text-default);
51
+ background: var(--surface-hover);
52
+ }
53
+ }
54
+
55
+ .tab-bar__item:active:not(.is-active) {
56
+ transform: scale(0.985);
57
+ transition: transform 80ms var(--ease-spring-snappy);
58
+ }
@@ -0,0 +1,39 @@
1
+ /* ================================================================
2
+ components/table.css
3
+ Tabell med header + rader. Tabular-nums for nummerkolumner.
4
+ ================================================================ */
5
+ .table {
6
+ width: 100%;
7
+ border-collapse: collapse;
8
+ font-size: var(--fs-13);
9
+ }
10
+
11
+ .table th {
12
+ font-size: var(--fs-10);
13
+ font-weight: var(--fw-medium);
14
+ letter-spacing: 0.1em;
15
+ text-transform: uppercase;
16
+ color: var(--text-muted);
17
+ text-align: left;
18
+ padding: var(--space-8) var(--space-6);
19
+ border-bottom: 1px solid var(--border-default);
20
+ }
21
+
22
+ .table td {
23
+ padding: var(--space-12) var(--space-6);
24
+ border-bottom: 1px solid var(--border-subtle);
25
+ color: var(--text-default);
26
+ }
27
+
28
+ .table tbody tr:last-child td { border-bottom: none; }
29
+
30
+ @media (hover: hover) and (pointer: fine) {
31
+ .table tbody tr:hover { background: var(--surface-hover); }
32
+ }
33
+
34
+ .table td.num,
35
+ .table th.num {
36
+ text-align: right;
37
+ font-weight: var(--fw-medium);
38
+ font-variant-numeric: tabular-nums;
39
+ }
@@ -0,0 +1,55 @@
1
+ /* ================================================================
2
+ components/upload-spinner.css
3
+ Stor loading-overlay som tacker viewport. Anvands for OCR-uploads,
4
+ AI-anrop och andra langa async-operations dar appen ar blockerad.
5
+
6
+ .upload-spinner-overlay ar fixed-positionerat overlay som visas
7
+ nar .is-visible-klassen togglas via JS.
8
+ ================================================================ */
9
+ .upload-spinner-overlay {
10
+ position: fixed;
11
+ inset: 0;
12
+ background: var(--backdrop-spinner);
13
+ -webkit-backdrop-filter: blur(8px);
14
+ backdrop-filter: blur(8px);
15
+ display: none;
16
+ align-items: center;
17
+ justify-content: center;
18
+ flex-direction: column;
19
+ gap: var(--space-20);
20
+ z-index: var(--z-overlay, 70);
21
+ padding: var(--space-32);
22
+ text-align: center;
23
+ }
24
+
25
+ .upload-spinner-overlay.is-visible { display: flex; }
26
+
27
+ .upload-spinner {
28
+ width: 56px;
29
+ height: 56px;
30
+ border: 3px solid var(--accent-a3);
31
+ border-top-color: var(--accent-9);
32
+ border-radius: var(--radius-full);
33
+ animation: upload-spinner-rotate var(--dur-slowest, 0.9s) linear infinite;
34
+ }
35
+
36
+ .upload-spinner-overlay__label {
37
+ font-size: var(--fs-15);
38
+ font-weight: var(--fw-medium);
39
+ color: var(--text-default);
40
+ letter-spacing: -0.01em;
41
+ }
42
+
43
+ .upload-spinner-overlay__hint {
44
+ font-size: var(--fs-12);
45
+ color: var(--text-muted);
46
+ max-width: 280px;
47
+ }
48
+
49
+ @keyframes upload-spinner-rotate {
50
+ to { transform: rotate(360deg); }
51
+ }
52
+
53
+ @media (prefers-reduced-motion: reduce) {
54
+ .upload-spinner { animation-duration: 2.4s; }
55
+ }
package/css/index.css CHANGED
@@ -13,10 +13,17 @@
13
13
  @import '@klodd/ds/css/apps/jubb.css';
14
14
  ================================================================ */
15
15
 
16
+ /* Foundation */
16
17
  @import './00-primitives.css';
17
18
  @import './10-semantic.css';
19
+ @import './utilities.css';
20
+
21
+ /* Base layer */
18
22
  @import './base/pwa.css';
19
23
  @import './base/typography.css';
24
+ @import './base/layout.css';
25
+
26
+ /* Komponenter */
20
27
  @import './components/button.css';
21
28
  @import './components/input.css';
22
29
  @import './components/badge.css';
@@ -30,3 +37,24 @@
30
37
  @import './components/progress.css';
31
38
  @import './components/tooltip.css';
32
39
  @import './components/dropdown.css';
40
+
41
+ /* v2.0.0 - flyttade fran app-repona */
42
+ @import './components/banner.css';
43
+ @import './components/panel.css';
44
+ @import './components/hub-card.css';
45
+ @import './components/stat.css';
46
+ @import './components/form.css';
47
+ @import './components/setting-row.css';
48
+ @import './components/collapsible.css';
49
+ @import './components/hbar.css';
50
+ @import './components/split-bar.css';
51
+ @import './components/hero.css';
52
+ @import './components/chip.css';
53
+ @import './components/avatar.css';
54
+ @import './components/list-row.css';
55
+ @import './components/table.css';
56
+ @import './components/auth.css';
57
+ @import './components/swipe-stack.css';
58
+ @import './components/inline-edit.css';
59
+ @import './components/upload-spinner.css';
60
+ @import './components/tab-bar.css';
@@ -0,0 +1,81 @@
1
+ /* ================================================================
2
+ utilities.css
3
+ Single-purpose helper-classes for sma layout-detaljer.
4
+ Bygger pa pixel-numerisk konvention (.m-0, .mt-12, .gap-8).
5
+
6
+ Ingen styling-logik - bara en CSS-property per klass.
7
+ ================================================================ */
8
+
9
+ /* Margin */
10
+ .m-0 { margin: 0; }
11
+ .mt-0 { margin-top: 0; }
12
+ .mt-4 { margin-top: var(--space-4); }
13
+ .mt-8 { margin-top: var(--space-8); }
14
+ .mt-12 { margin-top: var(--space-12); }
15
+ .mt-16 { margin-top: var(--space-16); }
16
+ .mt-20 { margin-top: var(--space-20); }
17
+ .mt-24 { margin-top: var(--space-24); }
18
+ .mb-0 { margin-bottom: 0; }
19
+ .mb-4 { margin-bottom: var(--space-4); }
20
+ .mb-8 { margin-bottom: var(--space-8); }
21
+ .mb-12 { margin-bottom: var(--space-12); }
22
+ .mb-16 { margin-bottom: var(--space-16); }
23
+ .mb-20 { margin-bottom: var(--space-20); }
24
+ .mb-24 { margin-bottom: var(--space-24); }
25
+
26
+ /* Padding */
27
+ .p-0 { padding: 0; }
28
+ .pt-0 { padding-top: 0; }
29
+ .pb-0 { padding-bottom: 0; }
30
+
31
+ /* Display */
32
+ .is-hidden { display: none !important; }
33
+ .flex { display: flex; }
34
+ .inline-flex { display: inline-flex; }
35
+
36
+ /* Width */
37
+ .w-full { width: 100%; }
38
+
39
+ /* Flex */
40
+ .gap-4 { gap: var(--space-4); }
41
+ .gap-8 { gap: var(--space-8); }
42
+ .gap-12 { gap: var(--space-12); }
43
+ .gap-16 { gap: var(--space-16); }
44
+ .flex-column { flex-direction: column; }
45
+ .align-center { align-items: center; }
46
+ .align-self-start { align-self: flex-start; }
47
+ .justify-between { justify-content: space-between; }
48
+
49
+ /* Position (CSP-migration: ersatter style="position: ...") */
50
+ .absolute { position: absolute; }
51
+ .absolute-fill { position: absolute; inset: 0; }
52
+ .relative { position: relative; }
53
+
54
+ /* Text */
55
+ .text-center { text-align: center; }
56
+ .text-right { text-align: right; }
57
+ .break-word { word-break: break-word; overflow-wrap: anywhere; }
58
+ .tabular-nums { font-variant-numeric: tabular-nums; }
59
+
60
+ /* Text-color (semantic shortcuts) */
61
+ .text-default { color: var(--text-default); }
62
+ .text-subtle { color: var(--text-subtle); }
63
+ .text-muted { color: var(--text-muted); }
64
+ .text-disabled { color: var(--text-disabled); }
65
+ .text-positive { color: var(--positive); }
66
+ .text-negative { color: var(--negative); }
67
+ .text-warning { color: var(--warning); }
68
+ .text-accent { color: var(--accent-text); }
69
+
70
+ /* Visibility */
71
+ .visually-hidden {
72
+ position: absolute;
73
+ width: 1px;
74
+ height: 1px;
75
+ padding: 0;
76
+ margin: -1px;
77
+ overflow: hidden;
78
+ clip: rect(0, 0, 0, 0);
79
+ white-space: nowrap;
80
+ border: 0;
81
+ }
@@ -0,0 +1,37 @@
1
+ /* bar-styles.js - applicerar dynamiska CSS-varden via data-attribut.
2
+ * CSP-sakert: JS-runtime bypass:ar HTML-parser-restriktioner pa
3
+ * style-src 'unsafe-inline'. Element kan deklarera dynamic dimensions
4
+ * via data-attribut istallet for inline style="..."-attribut.
5
+ *
6
+ * Stodda attribut:
7
+ * data-bar-width="N" sa style.width = N + '%'
8
+ * data-bar-height="N" sa style.height = N + '%'
9
+ * data-z-index="N" sa style.zIndex = N
10
+ *
11
+ * Re-init pa turbo:swap + turbo:navigated for SPA-flow sa nyrenderade
12
+ * element pa swapped-DOM aktiveras.
13
+ */
14
+ (function () {
15
+ 'use strict';
16
+
17
+ function applyBarStyles() {
18
+ document.querySelectorAll('[data-bar-width]').forEach(function (el) {
19
+ el.style.width = el.dataset.barWidth + '%';
20
+ });
21
+ document.querySelectorAll('[data-bar-height]').forEach(function (el) {
22
+ el.style.height = el.dataset.barHeight + '%';
23
+ });
24
+ document.querySelectorAll('[data-z-index]').forEach(function (el) {
25
+ el.style.zIndex = el.dataset.zIndex;
26
+ });
27
+ }
28
+
29
+ if (document.readyState === 'loading') {
30
+ document.addEventListener('DOMContentLoaded', applyBarStyles);
31
+ } else {
32
+ applyBarStyles();
33
+ }
34
+
35
+ document.addEventListener('turbo:swap', applyBarStyles);
36
+ document.addEventListener('turbo:navigated', applyBarStyles);
37
+ })();