@klodd/ds 5.7.0 → 5.8.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.
- package/bin/verify.js +147 -7
- package/css/00-primitives.css +8 -0
- package/css/base/interactions.css +5 -4
- package/css/base/layout.css +3 -3
- package/css/base/pwa.css +1 -1
- package/css/components/async-progress.css +1 -1
- package/css/components/auth.css +1 -1
- package/css/components/button.css +2 -2
- package/css/components/chip.css +6 -6
- package/css/components/dropdown.css +1 -1
- package/css/components/feedback.css +8 -8
- package/css/components/form.css +1 -1
- package/css/components/hero-roll.css +1 -1
- package/css/components/inline-edit.css +1 -1
- package/css/components/input.css +2 -1
- package/css/components/list-row.css +3 -3
- package/css/components/offline.css +3 -3
- package/css/components/overlay.css +4 -4
- package/css/components/panel.css +2 -2
- package/css/components/pill-nav.css +1 -1
- package/css/components/progress-bar.css +1 -1
- package/css/components/progress.css +1 -1
- package/css/components/setting-row.css +5 -4
- package/css/components/sheet-content.css +5 -5
- package/css/components/swipe-stack.css +2 -0
- package/css/components/tab-bar.css +1 -1
- package/css/components/upload-spinner.css +1 -0
- package/deprecated-classes.txt +10 -0
- package/package.json +2 -1
- package/references/05-open-decisions/0025-content-max-token-konsolidering.md +43 -0
- package/stylelint-plugin/index.js +58 -3
package/bin/verify.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/* @klodd/ds verify -
|
|
3
|
-
* Kör i CI: npx @klodd/ds verify
|
|
4
|
-
* Fail:ar med exit code 1 om drift detekteras.
|
|
2
|
+
/* @klodd/ds verify - validerar att en app stammer mot paketet.
|
|
3
|
+
* Kör i CI: npx @klodd/ds verify. Exit 1 vid problem.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
5
|
+
* Check 1 - fil-drift: diffar committade DS-filer mot paket-källan.
|
|
6
|
+
* exclude-fältet (v3.5.0+) i klodd-ds.json hoppas över.
|
|
7
|
+
* Check 2 - deprecade selektorer: letar i app-ägd kod (app/templates/
|
|
8
|
+
* + app/static/, exkl. synkade paket-filer i ds/) efter klass-
|
|
9
|
+
* ANVANDNING av namn listade i paketets deprecated-classes.txt.
|
|
10
|
+
* Kommentarer ignoreras - bara riktiga class=""/selektor/JS-strang-
|
|
11
|
+
* referenser flaggas. */
|
|
7
12
|
'use strict';
|
|
8
13
|
|
|
9
14
|
const fs = require('fs');
|
|
@@ -26,6 +31,7 @@ function hashFile(filePath) {
|
|
|
26
31
|
|
|
27
32
|
function listFilesRec(dir) {
|
|
28
33
|
const out = [];
|
|
34
|
+
if (!fs.existsSync(dir)) return out;
|
|
29
35
|
const stack = [{ abs: dir, rel: '' }];
|
|
30
36
|
while (stack.length) {
|
|
31
37
|
const { abs, rel } = stack.pop();
|
|
@@ -57,17 +63,151 @@ function diffDirs(sourceDir, targetRel, kind) {
|
|
|
57
63
|
return drifted;
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
// --- Deprecated-class-check ---
|
|
67
|
+
|
|
68
|
+
function escapeRe(s) {
|
|
69
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// deprecated-classes.txt: en CSS-klass per rad (utan punkt). Allt efter
|
|
73
|
+
// klassnamnet pa raden ar fri kommentar. Tomma rader + #-rader hoppas.
|
|
74
|
+
function loadDeprecatedClasses() {
|
|
75
|
+
const file = path.join(pkgDir, 'deprecated-classes.txt');
|
|
76
|
+
if (!fs.existsSync(file)) return [];
|
|
77
|
+
return fs.readFileSync(file, 'utf8')
|
|
78
|
+
.split('\n')
|
|
79
|
+
.map((line) => line.trim())
|
|
80
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
81
|
+
.map((line) => line.split(/\s+/)[0]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Returnerar varje rads kod med kommentarer borttagna. Flerrads
|
|
85
|
+
// /* */ (css/js), <!-- --> + {# #} (html) och radslut // (js).
|
|
86
|
+
function stripComments(content, lang) {
|
|
87
|
+
const blocks = {
|
|
88
|
+
css: [['/*', '*/']],
|
|
89
|
+
js: [['/*', '*/']],
|
|
90
|
+
html: [['<!--', '-->'], ['{#', '#}']],
|
|
91
|
+
}[lang] || [];
|
|
92
|
+
const lineMarker = lang === 'js' ? '//' : null;
|
|
93
|
+
const out = [];
|
|
94
|
+
let close = null;
|
|
95
|
+
for (const raw of content.split('\n')) {
|
|
96
|
+
let code = '';
|
|
97
|
+
let i = 0;
|
|
98
|
+
while (i < raw.length) {
|
|
99
|
+
if (close) {
|
|
100
|
+
const end = raw.indexOf(close, i);
|
|
101
|
+
if (end === -1) { i = raw.length; }
|
|
102
|
+
else { i = end + close.length; close = null; }
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
let bestPos = -1;
|
|
106
|
+
let bestOpenLen = 0;
|
|
107
|
+
let bestClose = null;
|
|
108
|
+
for (const [open, cl] of blocks) {
|
|
109
|
+
const p = raw.indexOf(open, i);
|
|
110
|
+
if (p !== -1 && (bestPos === -1 || p < bestPos)) {
|
|
111
|
+
bestPos = p;
|
|
112
|
+
bestOpenLen = open.length;
|
|
113
|
+
bestClose = cl;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const lc = lineMarker ? raw.indexOf(lineMarker, i) : -1;
|
|
117
|
+
if (lc !== -1 && (bestPos === -1 || lc < bestPos)) {
|
|
118
|
+
code += raw.slice(i, lc);
|
|
119
|
+
i = raw.length;
|
|
120
|
+
} else if (bestPos !== -1) {
|
|
121
|
+
code += raw.slice(i, bestPos);
|
|
122
|
+
i = bestPos + bestOpenLen;
|
|
123
|
+
close = bestClose;
|
|
124
|
+
} else {
|
|
125
|
+
code += raw.slice(i);
|
|
126
|
+
i = raw.length;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
out.push(code);
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Sann om raden (kommentarsbefriad) ANVANDER klassen: class=""-attribut
|
|
135
|
+
// (html), .selektor (css) eller klass-strang i JS.
|
|
136
|
+
function usesClass(code, cls, lang) {
|
|
137
|
+
const c = escapeRe(cls);
|
|
138
|
+
if (lang === 'css') {
|
|
139
|
+
return new RegExp('\\.' + c + '(?![\\w-])').test(code);
|
|
140
|
+
}
|
|
141
|
+
if (lang === 'js') {
|
|
142
|
+
return new RegExp("['\"`](?:[^'\"`]*\\.)?" + c + '(?![\\w-])').test(code);
|
|
143
|
+
}
|
|
144
|
+
if (lang === 'html') {
|
|
145
|
+
const attrs = code.match(/class\s*=\s*("[^"]*"|'[^']*')/gi) || [];
|
|
146
|
+
return attrs.some((a) => new RegExp('(^|[^\\w-])' + c + '(?![\\w-])').test(a));
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Synkade paket-filer (css/ds/<X>, js/ds/<X> dar <X> finns i paketet)
|
|
152
|
+
// hoppas - checken galler app-agd kod. App-agda domain-filer i ds/
|
|
153
|
+
// (ekonom.css, jubb.css, bundles/ osv.) behalls i kontrollen.
|
|
154
|
+
function grepDeprecated(classNames) {
|
|
155
|
+
const hits = [];
|
|
156
|
+
if (classNames.length === 0) return hits;
|
|
157
|
+
const LANG = { '.html': 'html', '.css': 'css', '.js': 'js' };
|
|
158
|
+
const pkgCss = new Set(listFilesRec(path.join(pkgDir, 'css')));
|
|
159
|
+
const pkgJs = new Set(listFilesRec(path.join(pkgDir, 'js')));
|
|
160
|
+
function isSyncedPackageFile(rel) {
|
|
161
|
+
if (rel.startsWith('css/ds/')) return pkgCss.has(rel.slice('css/ds/'.length));
|
|
162
|
+
if (rel.startsWith('js/ds/')) return pkgJs.has(rel.slice('js/ds/'.length));
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
for (const dir of ['app/templates', 'app/static']) {
|
|
166
|
+
const abs = path.join(process.cwd(), dir);
|
|
167
|
+
if (!fs.existsSync(abs)) continue;
|
|
168
|
+
for (const rel of listFilesRec(abs)) {
|
|
169
|
+
const lang = LANG[path.extname(rel)];
|
|
170
|
+
if (!lang) continue;
|
|
171
|
+
if (dir === 'app/static' && isSyncedPackageFile(rel)) continue;
|
|
172
|
+
const code = stripComments(fs.readFileSync(path.join(abs, rel), 'utf8'), lang);
|
|
173
|
+
code.forEach((line, idx) => {
|
|
174
|
+
for (const cls of classNames) {
|
|
175
|
+
if (usesClass(line, cls, lang)) {
|
|
176
|
+
hits.push(`${dir}/${rel}:${idx + 1} ${cls}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return hits;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Check 1: fil-drift mot paket-kallan ---
|
|
60
186
|
const cssDrift = diffDirs(path.join(pkgDir, 'css'), config.cssTarget, 'css');
|
|
61
187
|
const jsDrift = diffDirs(path.join(pkgDir, 'js'), config.jsTarget, 'js');
|
|
62
188
|
const allDrift = [...cssDrift, ...jsDrift];
|
|
63
189
|
|
|
190
|
+
// --- Check 2: deprecade selektorer i app-koden ---
|
|
191
|
+
const deprecatedHits = grepDeprecated(loadDeprecatedClasses());
|
|
192
|
+
|
|
193
|
+
let failed = false;
|
|
194
|
+
|
|
64
195
|
if (allDrift.length > 0) {
|
|
196
|
+
failed = true;
|
|
65
197
|
console.error('FAIL: @klodd/ds drift detekterad:');
|
|
66
198
|
allDrift.forEach((d) => console.error(' ' + d));
|
|
67
|
-
console.error('
|
|
68
|
-
process.exit(1);
|
|
199
|
+
console.error('Kör `npm run build:ds` för att synka.\n');
|
|
69
200
|
}
|
|
70
201
|
|
|
202
|
+
if (deprecatedHits.length > 0) {
|
|
203
|
+
failed = true;
|
|
204
|
+
console.error(`FAIL: ${deprecatedHits.length} referens(er) till deprecade klasser:`);
|
|
205
|
+
deprecatedHits.forEach((h) => console.error(' ' + h));
|
|
206
|
+
console.error('Se @klodd/ds deprecated-classes.txt för ersättningar.\n');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (failed) process.exit(1);
|
|
210
|
+
|
|
71
211
|
const excludedCount = exclude.size > 0 ? ` (${exclude.size} excluderade fran kontroll)` : '';
|
|
72
|
-
console.log(`OK: alla DS-filer matchar @klodd/ds-paketet${excludedCount}.`);
|
|
212
|
+
console.log(`OK: alla DS-filer matchar @klodd/ds-paketet${excludedCount}, 0 deprecade selektorer.`);
|
|
73
213
|
process.exit(0);
|
package/css/00-primitives.css
CHANGED
|
@@ -415,6 +415,14 @@
|
|
|
415
415
|
--max-w-nav-desktop: 720px;
|
|
416
416
|
|
|
417
417
|
--content-max-width: 640px;
|
|
418
|
+
--content-max-narrow: 480px; /* .main-content--narrow */
|
|
419
|
+
--content-max-default: 600px; /* .main-content (default) */
|
|
420
|
+
--content-max-wide: 800px; /* .main-content--wide */
|
|
421
|
+
|
|
422
|
+
/* Matt-constraints for komponenters width/height. */
|
|
423
|
+
--measure-text: 320px; /* max-width for brodtext */
|
|
424
|
+
--measure-ui-sm: 160px; /* min-width dropdown/input */
|
|
425
|
+
--measure-overlay: 480px; /* max-width overlay/dialog */
|
|
418
426
|
}
|
|
419
427
|
|
|
420
428
|
|
|
@@ -120,6 +120,7 @@ button, a, [role="button"],
|
|
|
120
120
|
top: 0;
|
|
121
121
|
left: 0;
|
|
122
122
|
right: 0;
|
|
123
|
+
/* stylelint-disable-next-line klodd/no-literal-dimension -- dekorativ hero-glow-gradienthojd, en-off */
|
|
123
124
|
height: 420px;
|
|
124
125
|
background: var(--hero-glow);
|
|
125
126
|
pointer-events: none;
|
|
@@ -204,8 +205,8 @@ body[data-first-load="true"] .bottom-nav {
|
|
|
204
205
|
transform: translate(-50%, -40px);
|
|
205
206
|
z-index: var(--z-overlay);
|
|
206
207
|
pointer-events: none;
|
|
207
|
-
width:
|
|
208
|
-
height:
|
|
208
|
+
width: var(--circle-size);
|
|
209
|
+
height: var(--circle-size);
|
|
209
210
|
border-radius: 50%;
|
|
210
211
|
background: var(--surface-strong);
|
|
211
212
|
display: flex;
|
|
@@ -215,8 +216,8 @@ body[data-first-load="true"] .bottom-nav {
|
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
.ptr-spinner {
|
|
218
|
-
width:
|
|
219
|
-
height:
|
|
219
|
+
width: var(--space-18);
|
|
220
|
+
height: var(--space-18);
|
|
220
221
|
border: 2px solid var(--text-muted);
|
|
221
222
|
border-top-color: var(--accent-9);
|
|
222
223
|
border-radius: 50%;
|
package/css/base/layout.css
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
Anvands som default-skelett pa varje vy.
|
|
5
5
|
================================================================ */
|
|
6
6
|
.main-content {
|
|
7
|
-
max-width: var(--content-max-default
|
|
7
|
+
max-width: var(--content-max-default);
|
|
8
8
|
margin: 0 auto;
|
|
9
9
|
padding: calc(var(--safe-top) + var(--space-16)) var(--space-14) var(--bottom-nav-clearance);
|
|
10
10
|
position: relative;
|
|
11
11
|
min-height: calc(100dvh - var(--bottom-nav-clearance));
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
.main-content--narrow { max-width: var(--content-max-narrow
|
|
15
|
-
.main-content--wide { max-width: var(--content-max-wide
|
|
14
|
+
.main-content--narrow { max-width: var(--content-max-narrow); }
|
|
15
|
+
.main-content--wide { max-width: var(--content-max-wide); }
|
|
16
16
|
|
|
17
17
|
.page-header {
|
|
18
18
|
display: flex;
|
package/css/base/pwa.css
CHANGED
package/css/components/auth.css
CHANGED
|
@@ -239,8 +239,8 @@
|
|
|
239
239
|
content: "";
|
|
240
240
|
position: absolute;
|
|
241
241
|
inset: 50% auto auto 50%;
|
|
242
|
-
width:
|
|
243
|
-
height:
|
|
242
|
+
width: var(--space-16);
|
|
243
|
+
height: var(--space-16);
|
|
244
244
|
margin: -8px 0 0 -8px;
|
|
245
245
|
border: 2px solid currentColor;
|
|
246
246
|
border-top-color: transparent;
|
package/css/components/chip.css
CHANGED
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
|
|
36
36
|
.chip::before {
|
|
37
37
|
content: '';
|
|
38
|
-
width:
|
|
39
|
-
height:
|
|
38
|
+
width: var(--space-6);
|
|
39
|
+
height: var(--space-6);
|
|
40
40
|
border-radius: var(--radius-full);
|
|
41
41
|
background: currentColor;
|
|
42
42
|
display: inline-block;
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
display: inline-flex;
|
|
63
63
|
align-items: center;
|
|
64
64
|
gap: var(--space-6);
|
|
65
|
-
height:
|
|
65
|
+
height: var(--space-32);
|
|
66
66
|
padding: 0 var(--space-12);
|
|
67
67
|
background: var(--surface-default);
|
|
68
68
|
border: 1px solid var(--border-subtle);
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
display: inline-flex;
|
|
92
92
|
align-items: center;
|
|
93
93
|
justify-content: center;
|
|
94
|
-
width:
|
|
95
|
-
height:
|
|
94
|
+
width: var(--space-18);
|
|
95
|
+
height: var(--space-18);
|
|
96
96
|
padding: 0;
|
|
97
97
|
background: transparent;
|
|
98
98
|
border: 0;
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
display: inline-flex;
|
|
193
193
|
align-items: center;
|
|
194
194
|
gap: var(--space-4);
|
|
195
|
-
height:
|
|
195
|
+
height: var(--circle-size);
|
|
196
196
|
padding: 0 var(--space-6);
|
|
197
197
|
background: var(--surface-default);
|
|
198
198
|
border: 1px solid var(--border-subtle);
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
font-size: var(--fs-14);
|
|
123
123
|
line-height: var(--lh-base);
|
|
124
124
|
color: var(--text-subtle);
|
|
125
|
-
max-width:
|
|
125
|
+
max-width: var(--measure-text);
|
|
126
126
|
margin: 0;
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
.skeleton--card {
|
|
161
|
-
height:
|
|
161
|
+
height: var(--space-80);
|
|
162
162
|
border-radius: var(--radius-14);
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -182,8 +182,8 @@
|
|
|
182
182
|
================================================================ */
|
|
183
183
|
.spinner {
|
|
184
184
|
display: inline-block;
|
|
185
|
-
width:
|
|
186
|
-
height:
|
|
185
|
+
width: var(--space-24);
|
|
186
|
+
height: var(--space-24);
|
|
187
187
|
border: 2px solid currentColor;
|
|
188
188
|
border-top-color: transparent;
|
|
189
189
|
border-radius: var(--radius-full);
|
|
@@ -193,14 +193,14 @@
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
.spinner--sm {
|
|
196
|
-
width:
|
|
197
|
-
height:
|
|
196
|
+
width: var(--space-16);
|
|
197
|
+
height: var(--space-16);
|
|
198
198
|
border-width: 2px;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
.spinner--lg {
|
|
202
|
-
width:
|
|
203
|
-
height:
|
|
202
|
+
width: var(--space-40);
|
|
203
|
+
height: var(--space-40);
|
|
204
204
|
border-width: 3px;
|
|
205
205
|
}
|
|
206
206
|
|
package/css/components/form.css
CHANGED
package/css/components/input.css
CHANGED
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
.textarea {
|
|
58
|
+
/* stylelint-disable-next-line klodd/no-literal-dimension -- textarea min-height, dokumenterat en-off */
|
|
58
59
|
min-height: 96px;
|
|
59
60
|
padding: var(--space-12) var(--space-14);
|
|
60
61
|
line-height: var(--lh-base);
|
|
@@ -136,7 +137,7 @@
|
|
|
136
137
|
.select.input--compact {
|
|
137
138
|
min-height: auto;
|
|
138
139
|
width: auto;
|
|
139
|
-
max-width:
|
|
140
|
+
max-width: var(--measure-ui-sm);
|
|
140
141
|
padding: var(--space-6) var(--space-10);
|
|
141
142
|
font-size: var(--fs-16);
|
|
142
143
|
}
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
gap: var(--space-12);
|
|
37
37
|
padding: var(--space-10) 0;
|
|
38
38
|
border-bottom: 1px solid var(--border-subtle);
|
|
39
|
-
min-height:
|
|
39
|
+
min-height: var(--space-56);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
.list-row:last-child { border-bottom: none; }
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.list-row__icon svg {
|
|
57
|
-
width:
|
|
58
|
-
height:
|
|
57
|
+
width: var(--space-18);
|
|
58
|
+
height: var(--space-18);
|
|
59
59
|
stroke: currentColor;
|
|
60
60
|
fill: none;
|
|
61
61
|
stroke-width: 2;
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.offline-icon {
|
|
28
|
-
width:
|
|
29
|
-
height:
|
|
28
|
+
width: var(--space-64);
|
|
29
|
+
height: var(--space-64);
|
|
30
30
|
background: var(--surface-raised);
|
|
31
31
|
border: 1px solid var(--border-default);
|
|
32
32
|
border-radius: var(--radius-14);
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
.offline-text {
|
|
48
48
|
font-size: var(--fs-14);
|
|
49
49
|
color: var(--text-subtle);
|
|
50
|
-
max-width:
|
|
50
|
+
max-width: var(--measure-text);
|
|
51
51
|
line-height: var(--lh-base);
|
|
52
52
|
margin-bottom: var(--space-8);
|
|
53
53
|
}
|
|
@@ -40,7 +40,7 @@ dialog.sheet::backdrop {
|
|
|
40
40
|
transform: translate(-50%, -50%);
|
|
41
41
|
z-index: var(--z-overlay);
|
|
42
42
|
width: calc(100vw - 2 * var(--space-20));
|
|
43
|
-
max-width:
|
|
43
|
+
max-width: var(--measure-overlay);
|
|
44
44
|
max-height: calc(100dvh - 2 * var(--space-40));
|
|
45
45
|
margin: 0;
|
|
46
46
|
padding: 0;
|
|
@@ -117,7 +117,7 @@ dialog.dialog:modal {
|
|
|
117
117
|
top: auto;
|
|
118
118
|
z-index: var(--z-overlay);
|
|
119
119
|
width: 100%;
|
|
120
|
-
max-width:
|
|
120
|
+
max-width: var(--content-max-default);
|
|
121
121
|
max-height: 90svh;
|
|
122
122
|
margin: 0 auto;
|
|
123
123
|
padding: var(--space-8) var(--space-20) calc(var(--space-28) + var(--safe-bottom));
|
|
@@ -136,8 +136,8 @@ dialog.dialog:modal {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
.sheet__handle {
|
|
139
|
-
width:
|
|
140
|
-
height:
|
|
139
|
+
width: var(--space-32);
|
|
140
|
+
height: var(--space-4);
|
|
141
141
|
margin: 0 auto var(--space-16);
|
|
142
142
|
background: var(--text-disabled);
|
|
143
143
|
border-radius: var(--radius-full);
|
package/css/components/panel.css
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
.panel__pill {
|
|
91
91
|
display: inline-flex;
|
|
92
92
|
align-items: center;
|
|
93
|
-
height:
|
|
93
|
+
height: var(--space-24);
|
|
94
94
|
padding: 0 var(--space-10);
|
|
95
95
|
background: var(--surface-default);
|
|
96
96
|
color: var(--text-muted);
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
.panel__step-badge {
|
|
120
120
|
display: inline-flex;
|
|
121
121
|
align-items: center;
|
|
122
|
-
height:
|
|
122
|
+
height: var(--space-20);
|
|
123
123
|
padding: 0 var(--space-8);
|
|
124
124
|
background: var(--surface-active);
|
|
125
125
|
color: var(--accent-text);
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
.setting-row__pill {
|
|
134
134
|
display: inline-flex;
|
|
135
135
|
align-items: center;
|
|
136
|
-
height:
|
|
136
|
+
height: var(--space-28);
|
|
137
137
|
padding: 0 var(--space-12);
|
|
138
138
|
background: var(--accent-9);
|
|
139
139
|
color: var(--text-on-accent);
|
|
@@ -158,7 +158,8 @@
|
|
|
158
158
|
.setting-toggle {
|
|
159
159
|
position: relative;
|
|
160
160
|
display: inline-block;
|
|
161
|
-
width:
|
|
161
|
+
width: var(--touch-min);
|
|
162
|
+
/* stylelint-disable-next-line klodd/no-literal-dimension -- toggle-track 26px (22px thumb + 2x2px inset), dokumenterat en-off */
|
|
162
163
|
height: 26px;
|
|
163
164
|
flex-shrink: 0;
|
|
164
165
|
}
|
|
@@ -185,8 +186,8 @@
|
|
|
185
186
|
position: absolute;
|
|
186
187
|
top: 2px;
|
|
187
188
|
left: 2px;
|
|
188
|
-
width:
|
|
189
|
-
height:
|
|
189
|
+
width: var(--space-22);
|
|
190
|
+
height: var(--space-22);
|
|
190
191
|
background: var(--surface-page);
|
|
191
192
|
border-radius: 50%;
|
|
192
193
|
transition: transform var(--dur-medium) var(--ease-spring-snappy);
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
background: var(--surface-sunken);
|
|
93
93
|
border: 1px solid var(--border-default);
|
|
94
94
|
border-radius: var(--radius-14);
|
|
95
|
-
height:
|
|
95
|
+
height: var(--space-48);
|
|
96
96
|
transition: background var(--dur-fast) var(--ease-spring-snappy);
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -309,8 +309,8 @@
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
.sheet__toggle-input {
|
|
312
|
-
width:
|
|
313
|
-
height:
|
|
312
|
+
width: var(--space-22);
|
|
313
|
+
height: var(--space-22);
|
|
314
314
|
accent-color: var(--accent-9);
|
|
315
315
|
cursor: pointer;
|
|
316
316
|
flex-shrink: 0;
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
.sheet__btn--save {
|
|
339
339
|
display: block;
|
|
340
340
|
width: 100%;
|
|
341
|
-
height:
|
|
341
|
+
height: var(--space-48);
|
|
342
342
|
margin: var(--space-8) 0 0;
|
|
343
343
|
background: var(--accent-9);
|
|
344
344
|
color: var(--text-on-accent);
|
|
@@ -372,7 +372,7 @@
|
|
|
372
372
|
.sheet__btn--delete {
|
|
373
373
|
display: block;
|
|
374
374
|
width: 100%;
|
|
375
|
-
height:
|
|
375
|
+
height: var(--touch-min);
|
|
376
376
|
margin: var(--space-8) 0 0;
|
|
377
377
|
background: transparent;
|
|
378
378
|
color: var(--accent-danger);
|
|
@@ -41,8 +41,10 @@
|
|
|
41
41
|
.swipe-stack {
|
|
42
42
|
position: relative;
|
|
43
43
|
width: 100%;
|
|
44
|
+
/* stylelint-disable-next-line klodd/no-literal-dimension -- swipe-stack-bredd, dokumenterat en-off */
|
|
44
45
|
max-width: 480px;
|
|
45
46
|
margin: 0 auto;
|
|
47
|
+
/* stylelint-disable-next-line klodd/no-literal-dimension -- swipe-stack-hojd, dokumenterat en-off */
|
|
46
48
|
height: 480px;
|
|
47
49
|
isolation: isolate;
|
|
48
50
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @klodd/ds deprecated-classes.txt
|
|
2
|
+
# En CSS-klass per rad (utan punkt). Text efter klassnamnet på raden
|
|
3
|
+
# är fri kommentar. Tomma rader och #-rader hoppas över.
|
|
4
|
+
# npx @klodd/ds verify greppar app/templates/ + app/static/ efter
|
|
5
|
+
# varje klassnamn och exit 1 vid träff.
|
|
6
|
+
|
|
7
|
+
sub-row (→ expandable-row__item matched-row, ADR 0002)
|
|
8
|
+
hero-amount (→ hero__amount, ADR 0019. /design-demon fixad, 0 träffar - kvar som påminnelse)
|
|
9
|
+
btn--negative (raderad, ingen ersättning)
|
|
10
|
+
tx-row (→ list-row)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klodd/ds",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.8.0",
|
|
4
4
|
"description": "Klodd shared design system - tokens, components, JS",
|
|
5
5
|
"main": "css/index.css",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"css/",
|
|
11
11
|
"js/",
|
|
12
12
|
"bin/",
|
|
13
|
+
"deprecated-classes.txt",
|
|
13
14
|
"stylelint-plugin/",
|
|
14
15
|
"SKILL.md",
|
|
15
16
|
"references/"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 0025 - content-max token-konsolidering
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Pending.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Tre överlappande page-width-tokenfamiljer finns i paketet efter
|
|
10
|
+
width/height-arbetet (5.8.0):
|
|
11
|
+
|
|
12
|
+
1. `--content-max-*` (`-narrow` 480px, `-default` 600px, `-wide`
|
|
13
|
+
800px) - definierade i 00-primitives.css, konsumeras av
|
|
14
|
+
base/layout.css (`.main-content`-varianterna).
|
|
15
|
+
2. `--content-max-width: 640px` - orphan i 00-primitives.css, noll
|
|
16
|
+
användning i paketet eller apparna.
|
|
17
|
+
3. `--max-w-*` (sex tokens: `-mobile` 600, `-tablet` 680, `-desktop`
|
|
18
|
+
1280, `-form` 560, `-bottom-nav` 572, `-nav-desktop` 720) i
|
|
19
|
+
00-primitives.css. `--max-w-mobile: 600px` överlappar exakt
|
|
20
|
+
`--content-max-default: 600px`.
|
|
21
|
+
|
|
22
|
+
## Decision needed
|
|
23
|
+
|
|
24
|
+
Konsolidera till en page-width-tokenfamilj: behåll sannolikt
|
|
25
|
+
`--content-max-*`, radera orphan `--content-max-width`, mappa eller
|
|
26
|
+
radera `--max-w-*`-familjen.
|
|
27
|
+
|
|
28
|
+
## Considered options
|
|
29
|
+
|
|
30
|
+
### A. Konsolidera nu
|
|
31
|
+
Risk: `--max-w-*` och `--content-max-width` har okänd konsument-yta.
|
|
32
|
+
Apparnas domain-CSS och templates greppades inte. Att radera eller
|
|
33
|
+
mappa dem kan ge visuell drift på okänt antal vyer.
|
|
34
|
+
|
|
35
|
+
### B. Skjut till separat sprint (vald interim)
|
|
36
|
+
Lämna alla tre familjer orörda i 5.8.0. Konsolidera när
|
|
37
|
+
konsument-ytan kartlagts.
|
|
38
|
+
|
|
39
|
+
## Pending decision criteria
|
|
40
|
+
|
|
41
|
+
Kräver en survey av `--max-w-*`- och `--content-max-width`-användning
|
|
42
|
+
i båda app-repona (CSS + templates) innan konsolidering. Tas i egen
|
|
43
|
+
sprint.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* @klodd/ds stylelint-plugin
|
|
2
2
|
*
|
|
3
3
|
* Custom design-rule-validation som stylelint-config-standard inte
|
|
4
|
-
* tacker.
|
|
4
|
+
* tacker. Fyra regler, alla "klodd/"-prefixade:
|
|
5
5
|
*
|
|
6
6
|
* - klodd/no-forbidden-radius:
|
|
7
7
|
* border-radius maste vara en av {var(--radius-4), var(--radius-14),
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
* review mot ADR 0017-tabellen - plugin validerar bara token-
|
|
22
22
|
* existens, inte semantisk val per komponent.
|
|
23
23
|
*
|
|
24
|
+
* - klodd/no-literal-dimension:
|
|
25
|
+
* width/height/min/max-width/height maste anvanda token (var--*)
|
|
26
|
+
* eller relativ enhet. Hardkodad px flaggas, utom 1px/3px
|
|
27
|
+
* (hairlines + dekorativ dot). utilities.css exkluderas helt.
|
|
28
|
+
*
|
|
24
29
|
* Konfiguration i app-repos:
|
|
25
30
|
* .stylelintrc.json:
|
|
26
31
|
* {
|
|
@@ -28,7 +33,8 @@
|
|
|
28
33
|
* "rules": {
|
|
29
34
|
* "klodd/no-forbidden-radius": true,
|
|
30
35
|
* "klodd/no-forbidden-shadow": true,
|
|
31
|
-
* "klodd/no-forbidden-padding": true
|
|
36
|
+
* "klodd/no-forbidden-padding": true,
|
|
37
|
+
* "klodd/no-literal-dimension": true
|
|
32
38
|
* }
|
|
33
39
|
* }
|
|
34
40
|
*/
|
|
@@ -66,9 +72,17 @@ const SHADOW_INSET = /\binset\b/i;
|
|
|
66
72
|
const SHADOW_FOCUS_RING = /^0\s+0\s+0\s+\d+px\s+(var\(--accent-a\d+\)|color-mix\(.+\))/i;
|
|
67
73
|
const SHADOW_CARD_TOKEN = /^var\(--shadow-card\)$/i;
|
|
68
74
|
|
|
75
|
+
// no-literal-dimension: width/height-varden som INTE ar hardkodad px.
|
|
76
|
+
const DIMENSION_PROPS = /^(min-|max-)?(width|height)$/;
|
|
77
|
+
const DIMENSION_KEYWORD = /^(0|auto|none|inherit|initial|unset|fit-content|min-content|max-content)$/i;
|
|
78
|
+
const DIMENSION_REL_UNIT = /^[\d.]+(%|vw|vh|dvh|svh|vmin|vmax|rem)$/i;
|
|
79
|
+
const DIMENSION_PX = /^[\d.]+px$/i;
|
|
80
|
+
const DIMENSION_PX_OK = /^(1|3)px$/;
|
|
81
|
+
|
|
69
82
|
const RADIUS_RULE = 'klodd/no-forbidden-radius';
|
|
70
83
|
const SHADOW_RULE = 'klodd/no-forbidden-shadow';
|
|
71
84
|
const PADDING_RULE = 'klodd/no-forbidden-padding';
|
|
85
|
+
const DIMENSION_RULE = 'klodd/no-literal-dimension';
|
|
72
86
|
|
|
73
87
|
const messages = {
|
|
74
88
|
radius: stylelint.utils.ruleMessages(RADIUS_RULE, {
|
|
@@ -83,6 +97,10 @@ const messages = {
|
|
|
83
97
|
forbidden: (value) =>
|
|
84
98
|
`Forbjudet padding-varde "${value}" - anvand var(--space-N) (existerande token i 00-primitives.css). Hardkodade px-varden inte tillatna.`,
|
|
85
99
|
}),
|
|
100
|
+
dimension: stylelint.utils.ruleMessages(DIMENSION_RULE, {
|
|
101
|
+
forbidden: (value) =>
|
|
102
|
+
`Forbjudet width/height-varde "${value}" - anvand var(--space-N) | var(--measure-*) | var(--circle-size*) | var(--touch-*). Hardkodad px inte tillaten (undantag: 1px/3px).`,
|
|
103
|
+
}),
|
|
86
104
|
};
|
|
87
105
|
|
|
88
106
|
function checkRadius (root, result, primary) {
|
|
@@ -153,6 +171,30 @@ function checkPadding (root, result, primary) {
|
|
|
153
171
|
});
|
|
154
172
|
}
|
|
155
173
|
|
|
174
|
+
function checkDimension (root, result, primary) {
|
|
175
|
+
if (!primary) return;
|
|
176
|
+
// utilities.css ar fixed-dimension-utility-lagret - exkluderas helt.
|
|
177
|
+
const file = root.source && root.source.input && root.source.input.file;
|
|
178
|
+
if (file && (file.endsWith('/utilities.css') || file.endsWith('\\utilities.css'))) return;
|
|
179
|
+
root.walkDecls(DIMENSION_PROPS, (decl) => {
|
|
180
|
+
const value = decl.value.trim();
|
|
181
|
+
if (!value) return;
|
|
182
|
+
if (DIMENSION_KEYWORD.test(value)) return;
|
|
183
|
+
if (/^var\(/i.test(value)) return;
|
|
184
|
+
if (/^calc\(/i.test(value)) return;
|
|
185
|
+
if (DIMENSION_REL_UNIT.test(value)) return;
|
|
186
|
+
if (DIMENSION_PX_OK.test(value)) return;
|
|
187
|
+
if (DIMENSION_PX.test(value)) {
|
|
188
|
+
stylelint.utils.report({
|
|
189
|
+
ruleName: DIMENSION_RULE,
|
|
190
|
+
result,
|
|
191
|
+
node: decl,
|
|
192
|
+
message: messages.dimension.forbidden(value),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
156
198
|
const radiusPlugin = stylelint.createPlugin(RADIUS_RULE, (primary) => {
|
|
157
199
|
return (root, result) => {
|
|
158
200
|
const validOptions = stylelint.utils.validateOptions(result, RADIUS_RULE, {
|
|
@@ -192,4 +234,17 @@ const paddingPlugin = stylelint.createPlugin(PADDING_RULE, (primary) => {
|
|
|
192
234
|
paddingPlugin.ruleName = PADDING_RULE;
|
|
193
235
|
paddingPlugin.messages = messages.padding;
|
|
194
236
|
|
|
195
|
-
|
|
237
|
+
const dimensionPlugin = stylelint.createPlugin(DIMENSION_RULE, (primary) => {
|
|
238
|
+
return (root, result) => {
|
|
239
|
+
const validOptions = stylelint.utils.validateOptions(result, DIMENSION_RULE, {
|
|
240
|
+
actual: primary,
|
|
241
|
+
possible: [true, false],
|
|
242
|
+
});
|
|
243
|
+
if (!validOptions) return;
|
|
244
|
+
checkDimension(root, result, primary);
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
dimensionPlugin.ruleName = DIMENSION_RULE;
|
|
248
|
+
dimensionPlugin.messages = messages.dimension;
|
|
249
|
+
|
|
250
|
+
module.exports = [radiusPlugin, shadowPlugin, paddingPlugin, dimensionPlugin];
|