@klodd/ds 3.4.5 → 3.5.1

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/SKILL.md CHANGED
@@ -7,7 +7,8 @@ description: Design memory for the @klodd/ds shared design system across all Klo
7
7
 
8
8
  ## Vad detta är
9
9
  Gemensamt designsystem på npm (`@klodd/ds@3.x`). Tre-lagers tokens,
10
- 33+ komponenter, konsumeras av Jubb och Ekonom. Fler appar planerade.
10
+ 34+ komponenter (sheet-content tillagd v3.5.1), konsumeras av Jubb
11
+ och Ekonom. Fler appar planerade.
11
12
  Denna Skill fångar både reglerna ("vad") och resonemanget ("varför").
12
13
 
13
14
  ## Läs i denna ordning
@@ -76,4 +77,29 @@ på push och PR.
76
77
  Läs relevant ADR. Täcks frågan inte av något ADR — flagga det explicit:
77
78
  "Jag ser inget beslut på detta — ska vi skapa ett i references/05-open-decisions/?"
78
79
 
79
- Updated: 2026-05-08 (matchar @klodd/ds 3.4.1)
80
+ ## Distribution
81
+
82
+ Källan är `~/dev/klodd-ds/` (committad i git, synkas mellan maskiner).
83
+ Skillen aktiveras via symlink på varje maskin - inga kopior i app-repos.
84
+
85
+ **Mac:**
86
+ ```bash
87
+ mkdir -p ~/.claude/skills/
88
+ ln -s ~/dev/klodd-ds ~/.claude/skills/klodd-ds
89
+ ```
90
+
91
+ **Windows:**
92
+ ```cmd
93
+ mklink /D %USERPROFILE%\.claude\skills\klodd-ds %USERPROFILE%\dev\klodd-ds
94
+ ```
95
+
96
+ En källa, alla projekt ser samma skill automatiskt. Ändringar i
97
+ `~/dev/klodd-ds/SKILL.md`, `POLICY.md` eller `references/` syns
98
+ omedelbart i nästa CC-session - ingen synk-fas behövs.
99
+
100
+ Updated: 2026-05-08 (matchar @klodd/ds 3.5.1 - sprint C fas 1: ny
101
+ sheet-content.css-komponent (24 BEM-klasser för Sprint-65-pattern),
102
+ foreman-typografi-utokning (.text-caption/-tiny/-num/-num--display/-positive/-warning),
103
+ btn-modifiers (--block, --add), och utokningar i panel/setting-row/chip).
104
+ ds/components.css raderingen i Ekonom rollback:ad samma kvall efter audit -
105
+ template-migration (sprint C fas 2) ar nasta steg.)
package/bin/sync.js CHANGED
@@ -2,7 +2,11 @@
2
2
  /* @klodd/ds sync - kopierar paketets CSS och JS till app-repot.
3
3
  * Kör: npx @klodd/ds sync
4
4
  * Förutsätter att CWD är app-repots rot.
5
- * Läser cssTarget/jsTarget från klodd-ds.json i CWD, eller använder default.
5
+ * Läser cssTarget/jsTarget/exclude från klodd-ds.json i CWD, eller använder default.
6
+ *
7
+ * exclude-fältet (v3.5.0+) är en lista av relativa fil-paths under css/ eller
8
+ * js/ som hoppas över vid kopiering. Användbart när en app inte konsumerar en
9
+ * komponent som finns i paketet (t.ex. apps/ekonom.css i Jubbs repo).
6
10
  *
7
11
  * Cross-platform via fs.cpSync (Node 16.7+) - inte POSIX cp -r. */
8
12
  'use strict';
@@ -17,17 +21,39 @@ const jsSource = path.join(pkgDir, 'js');
17
21
  const configPath = path.join(process.cwd(), 'klodd-ds.json');
18
22
  const config = fs.existsSync(configPath)
19
23
  ? JSON.parse(fs.readFileSync(configPath, 'utf8'))
20
- : { cssTarget: 'app/static/css/ds', jsTarget: 'app/static/js/ds' };
24
+ : { cssTarget: 'app/static/css/ds', jsTarget: 'app/static/js/ds', exclude: [] };
21
25
 
22
26
  const cssTarget = path.join(process.cwd(), config.cssTarget);
23
27
  const jsTarget = path.join(process.cwd(), config.jsTarget);
28
+ const exclude = new Set(config.exclude || []);
24
29
 
25
30
  fs.mkdirSync(cssTarget, { recursive: true });
26
31
  fs.mkdirSync(jsTarget, { recursive: true });
27
32
 
28
- fs.cpSync(cssSource, cssTarget, { recursive: true });
33
+ function copyDirFiltered(srcRoot, dstRoot, kind) {
34
+ const stack = [{ abs: srcRoot, rel: '' }];
35
+ while (stack.length) {
36
+ const { abs, rel } = stack.pop();
37
+ for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
38
+ const childAbs = path.join(abs, entry.name);
39
+ const childRel = rel ? path.posix.join(rel, entry.name) : entry.name;
40
+ const excludeKey = `${kind}/${childRel}`;
41
+ if (exclude.has(excludeKey) || exclude.has(childRel)) continue;
42
+ const childDst = path.join(dstRoot, childRel);
43
+ if (entry.isDirectory()) {
44
+ fs.mkdirSync(childDst, { recursive: true });
45
+ stack.push({ abs: childAbs, rel: childRel });
46
+ } else if (entry.isFile()) {
47
+ fs.copyFileSync(childAbs, childDst);
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ copyDirFiltered(cssSource, cssTarget, 'css');
29
54
  if (fs.existsSync(jsSource)) {
30
- fs.cpSync(jsSource, jsTarget, { recursive: true });
55
+ copyDirFiltered(jsSource, jsTarget, 'js');
31
56
  }
32
57
 
33
- console.log(`OK @klodd/ds synkad till ${config.cssTarget} och ${config.jsTarget}`);
58
+ const excludedCount = exclude.size > 0 ? ` (${exclude.size} excluderade)` : '';
59
+ console.log(`OK @klodd/ds synkad till ${config.cssTarget} och ${config.jsTarget}${excludedCount}`);
package/bin/verify.js CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /* @klodd/ds verify - diff:ar committade DS-filer mot paket-källan.
3
3
  * Kör i CI: npx @klodd/ds verify
4
- * Fail:ar med exit code 1 om drift detekteras. */
4
+ * Fail:ar med exit code 1 om drift detekteras.
5
+ *
6
+ * exclude-fältet (v3.5.0+) i klodd-ds.json hoppas över vid diff. */
5
7
  'use strict';
6
8
 
7
9
  const fs = require('fs');
@@ -12,7 +14,9 @@ const pkgDir = path.dirname(require.resolve('@klodd/ds/package.json'));
12
14
  const configPath = path.join(process.cwd(), 'klodd-ds.json');
13
15
  const config = fs.existsSync(configPath)
14
16
  ? JSON.parse(fs.readFileSync(configPath, 'utf8'))
15
- : { cssTarget: 'app/static/css/ds', jsTarget: 'app/static/js/ds' };
17
+ : { cssTarget: 'app/static/css/ds', jsTarget: 'app/static/js/ds', exclude: [] };
18
+
19
+ const exclude = new Set(config.exclude || []);
16
20
 
17
21
  function hashFile(filePath) {
18
22
  return crypto.createHash('sha256')
@@ -27,7 +31,7 @@ function listFilesRec(dir) {
27
31
  const { abs, rel } = stack.pop();
28
32
  for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
29
33
  const childAbs = path.join(abs, entry.name);
30
- const childRel = rel ? path.join(rel, entry.name) : entry.name;
34
+ const childRel = rel ? path.posix.join(rel, entry.name) : entry.name;
31
35
  if (entry.isDirectory()) stack.push({ abs: childAbs, rel: childRel });
32
36
  else if (entry.isFile()) out.push(childRel);
33
37
  }
@@ -35,11 +39,13 @@ function listFilesRec(dir) {
35
39
  return out;
36
40
  }
37
41
 
38
- function diffDirs(sourceDir, targetRel) {
42
+ function diffDirs(sourceDir, targetRel, kind) {
39
43
  const drifted = [];
40
44
  if (!fs.existsSync(sourceDir)) return drifted;
41
45
  const targetAbs = path.join(process.cwd(), targetRel);
42
46
  for (const file of listFilesRec(sourceDir)) {
47
+ const excludeKey = `${kind}/${file}`;
48
+ if (exclude.has(excludeKey) || exclude.has(file)) continue;
43
49
  const src = path.join(sourceDir, file);
44
50
  const tgt = path.join(targetAbs, file);
45
51
  if (!fs.existsSync(tgt)) {
@@ -51,8 +57,8 @@ function diffDirs(sourceDir, targetRel) {
51
57
  return drifted;
52
58
  }
53
59
 
54
- const cssDrift = diffDirs(path.join(pkgDir, 'css'), config.cssTarget);
55
- const jsDrift = diffDirs(path.join(pkgDir, 'js'), config.jsTarget);
60
+ const cssDrift = diffDirs(path.join(pkgDir, 'css'), config.cssTarget, 'css');
61
+ const jsDrift = diffDirs(path.join(pkgDir, 'js'), config.jsTarget, 'js');
56
62
  const allDrift = [...cssDrift, ...jsDrift];
57
63
 
58
64
  if (allDrift.length > 0) {
@@ -62,5 +68,6 @@ if (allDrift.length > 0) {
62
68
  process.exit(1);
63
69
  }
64
70
 
65
- console.log('OK: alla DS-filer matchar @klodd/ds-paketet.');
71
+ const excludedCount = exclude.size > 0 ? ` (${exclude.size} excluderade fran kontroll)` : '';
72
+ console.log(`OK: alla DS-filer matchar @klodd/ds-paketet${excludedCount}.`);
66
73
  process.exit(0);
@@ -409,98 +409,14 @@
409
409
 
410
410
 
411
411
  /* ================================================================
412
- ==== LEGACY ALIASES
413
- Bakatkompat-skikt sa befintliga komponenter inte krasar under
414
- migration. Mappar gamla namn mot nya semantic tokens.
415
-
416
- Migration sker gradvis: nar en komponent rors, byt fran legacy-namn
417
- till nytt semantic-namn. Behall denna sektion tills alla call-sites
418
- ar migrerade. Da kan den raderas och DECISIONS.md uppdateras.
412
+ ==== OVERLAY TINTS
413
+ Transparenta tints för drag-overlays, hover-states dark surfaces
414
+ och liknande där en alpha-pixel-shift räcker. Inte permanenta
415
+ surface-nivåer (de ligger i SURFACES-sektionen ovan).
419
416
  ================================================================ */
420
417
  :root {
421
- /* Surfaces (gamla overlay-skala) -> mappa mot nya neutral-stegen. */
422
418
  --surface-faint: color-mix(in oklch, white 2%, transparent);
423
419
  --surface-subtle: color-mix(in oklch, white 4%, transparent);
424
420
  --surface-medium: color-mix(in oklch, white 8%, transparent);
425
421
  --surface-strong: color-mix(in oklch, white 12%, transparent);
426
-
427
- /* Bakgrunder - gamla bg-* mappas till nya surface-*. */
428
- --bg-sunken: var(--surface-sunken);
429
- --bg-base: var(--surface-page);
430
- --bg-surface: var(--surface-raised);
431
- --bg-raised: var(--surface-raised);
432
- --bg-hover: var(--surface-hover);
433
-
434
- /* Text - gamla text-primary/secondary/tertiary/faint. */
435
- --text-primary: var(--text-default);
436
- --text-secondary: var(--text-subtle);
437
- --text-tertiary: var(--text-muted);
438
- --text-faint: var(--text-disabled);
439
-
440
- /* Accent - gamla --accent (singular) mappas mot nya --accent-9.
441
- --accent-dim/-border foljde gamla 20%/40%-konventionen, mappas
442
- nu mot Radix-skalans steg 3 (subtil) och 6 (border-emphasis). */
443
- --accent: var(--accent-9);
444
- --accent-hover: var(--accent-10);
445
- --accent-strong: var(--accent-9);
446
- --accent-secondary: var(--accent-9);
447
- --accent-dim: var(--accent-a3);
448
- --accent-border: var(--accent-a6);
449
-
450
- /* Accent-link - gamla cirkelknapps-tokens. Mappas mot accent-systemet.
451
- Gamla 10/20/40%-stegen mappas mot nya Radix-stegen 2/3/6. */
452
- --accent-link: var(--accent-text);
453
- --accent-link-bg: var(--accent-a2);
454
- --accent-link-bg-hover: var(--accent-a3);
455
- --accent-link-border: var(--accent-a3);
456
- --accent-link-border-hover: var(--accent-a6);
457
-
458
- /* Audit-aliaser fran 2026-05-07 - ofta refererade utan att vara definierade. */
459
- --text: var(--text-default);
460
- --border: var(--border-default);
461
- --surface-2: var(--surface-default);
462
- --surface-3: var(--surface-raised);
463
- --accent-rgba: var(--accent-a3);
464
-
465
- /* Radius - gamla t-shirt-namn mappas mot nya pixel-numeriska. */
466
- --radius-xs: var(--radius-2);
467
- --radius-sm: var(--radius-4);
468
- --radius-md: var(--radius-6);
469
- --radius-lg: var(--radius-12);
470
- --radius-xl: var(--radius-14);
471
- --radius-2xl: var(--radius-16);
472
- --radius-3xl: var(--radius-20);
473
- --radius-4xl: var(--radius-24);
474
- --radius-pill: var(--radius-full);
475
-
476
- /* Font-weight: --fw-semibold borttagen 2026-05-07 per 400/500-policy.
477
- Aktiva call-sites bytte till var(--fw-medium) explicit. Se DECISIONS.md. */
478
-
479
- /* Kategori-paletter (Ekonom-only). Behalls for shared-tokens-paritet
480
- med Ekonom-repot, oanvanda i Jubb. */
481
- --primitive-cat-mat: color-mix(in oklch, var(--purple-9) 60%, transparent);
482
- --primitive-cat-el: oklch(0.72 0.135 245);
483
- --primitive-cat-vatten: oklch(0.78 0.110 200);
484
- --primitive-cat-avgift: var(--green-9);
485
- --primitive-cat-noje: oklch(0.72 0.150 350);
486
- --primitive-cat-amort: oklch(0.72 0.135 160);
487
- --primitive-cat-ranta: oklch(0.72 0.150 50);
488
- --primitive-cat-privat: var(--gray-9);
489
-
490
- --cat-mat: color-mix(in oklch, var(--purple-9) 15%, transparent);
491
- --cat-mat-icon: var(--primitive-cat-mat);
492
- --cat-el: color-mix(in oklch, var(--blue-9) 15%, transparent);
493
- --cat-el-icon: var(--primitive-cat-el);
494
- --cat-vatten: color-mix(in oklch, oklch(0.78 0.110 200) 15%, transparent);
495
- --cat-vatten-icon: var(--primitive-cat-vatten);
496
- --cat-avgift: color-mix(in oklch, var(--green-9) 15%, transparent);
497
- --cat-avgift-icon: var(--primitive-cat-avgift);
498
- --cat-noje: color-mix(in oklch, oklch(0.72 0.150 350) 15%, transparent);
499
- --cat-noje-icon: var(--primitive-cat-noje);
500
- --cat-amortering: color-mix(in oklch, oklch(0.72 0.135 160) 15%, transparent);
501
- --cat-amort-icon: var(--primitive-cat-amort);
502
- --cat-ranta: color-mix(in oklch, oklch(0.72 0.150 50) 15%, transparent);
503
- --cat-ranta-icon: var(--primitive-cat-ranta);
504
- --cat-privat: color-mix(in oklch, var(--gray-9) 12%, transparent);
505
- --cat-privat-icon: var(--primitive-cat-privat);
506
422
  }
@@ -26,9 +26,7 @@
26
26
 
27
27
  Konvention: --surface-raised = appens <color>-2. Gäller alla
28
28
  Klodd-appar (Jubb -> blue-2, Ekonom -> plum-2). Korten ligger
29
- subtilt over page-bakgrunden utan att bli för ljus. Tidigare
30
- använde Ekonom --plum-2-5 (mellanvarde) - nu linjerat med
31
- samma regel som Jubb. */
29
+ subtilt över page-bakgrunden utan att bli för ljusa. */
32
30
  --surface-page: var(--plum-1); /* #181118 */
33
31
  --surface-default: var(--plum-2); /* #201320 */
34
32
  --surface-raised: var(--plum-2); /* #201320 - kort, paneler */
@@ -125,3 +125,61 @@
125
125
  .text-on-accent {
126
126
  color: var(--text-on-accent);
127
127
  }
128
+
129
+
130
+ /* ================================================================
131
+ ==== FOREMAN-VARIANTER (v3.5.1, 2026-05-08)
132
+ Tillagda fran Ekonoms ds/components.css som flyttades till paketet
133
+ nar sprint C-migration paboorjades. Generiska namn med semantic
134
+ tokens.
135
+ ================================================================ */
136
+
137
+ /* Smaa metadata-text under content (citations, byline, helptext).
138
+ Skiljer sig fran .text-meta genom explicit letter-spacing for
139
+ lasbarhet pa 11px. */
140
+ .text-caption {
141
+ font-size: var(--fs-11);
142
+ font-weight: var(--fw-regular);
143
+ line-height: var(--lh-base);
144
+ letter-spacing: 0.01em;
145
+ color: var(--text-muted);
146
+ }
147
+
148
+ /* Mycket liten uppercase-tracker (10px). Anvands for category-rubriker,
149
+ meta-info ovanfor titel etc. Liknande tracking som .heading-4 men
150
+ mindre. */
151
+ .text-tiny {
152
+ font-size: var(--fs-10);
153
+ font-weight: var(--fw-medium);
154
+ letter-spacing: 0.08em;
155
+ text-transform: uppercase;
156
+ color: var(--text-muted);
157
+ }
158
+
159
+ /* Inline-numerik med tabular-nums (14px). Default-storlek for siffror
160
+ inom panel-titlar, setting-row-amounts, kategori-totaler. */
161
+ .text-num {
162
+ font-size: var(--fs-14);
163
+ font-weight: var(--fw-medium);
164
+ line-height: var(--lh-snug);
165
+ letter-spacing: -0.015em;
166
+ font-variant-numeric: tabular-nums;
167
+ }
168
+
169
+ /* Display-numerik (32px). Anvands for stora siffror i kort/panels.
170
+ Komposition: <span class="text-num text-num--display">. */
171
+ .text-num--display {
172
+ font-size: var(--fs-32);
173
+ line-height: var(--lh-tight);
174
+ letter-spacing: -0.02em;
175
+ }
176
+
177
+ /* Color-aliaser - foljer paketets befintliga .text-on-accent-konvention.
178
+ Anvands for inline emphasis i text-block. */
179
+ .text-positive {
180
+ color: var(--positive);
181
+ }
182
+
183
+ .text-warning {
184
+ color: var(--warning);
185
+ }
@@ -165,6 +165,39 @@
165
165
  }
166
166
 
167
167
 
168
+ /* ================================================================
169
+ ==== BLOCK-MODIFIER (v3.5.1)
170
+ Full-bredd-knapp for sheet-actions, danger-block, primary-cta i
171
+ form-flode. Komponerar med variant-modifiers (.btn--primary,
172
+ .btn--danger, .btn--add).
173
+ ================================================================ */
174
+ .btn--block {
175
+ display: block;
176
+ width: 100%;
177
+ }
178
+
179
+
180
+ /* ================================================================
181
+ ==== ADD-MODIFIER (v3.5.1)
182
+ "Lagg till"-knapp med dashed border. Signalerar tomt slot.
183
+ Komposition: .btn .btn--add .btn--block (full-bredd add-knapp).
184
+ ================================================================ */
185
+ .btn--add {
186
+ background: transparent;
187
+ color: var(--text-subtle);
188
+ border-color: var(--text-disabled);
189
+ border-style: dashed;
190
+ }
191
+
192
+ @media (hover: hover) and (pointer: fine) {
193
+ .btn--add:hover {
194
+ background: var(--surface-hover);
195
+ color: var(--text-default);
196
+ border-color: var(--text-muted);
197
+ }
198
+ }
199
+
200
+
168
201
  /* ================================================================
169
202
  ==== LOADING-STATE
170
203
  Doljer text och visar spinner via ::after. Behaller knappens
@@ -65,6 +65,20 @@
65
65
  white-space: nowrap;
66
66
  }
67
67
 
68
+ /* Text-element inom chip-list-item. Tight line-height for visual
69
+ centering med padding. v3.5.1. */
70
+ .chip-list__text {
71
+ line-height: 1;
72
+ }
73
+
74
+ /* Form-element inom chip-list (when item ar inline-form med delete-btn).
75
+ Default UA-margin nollstalls. v3.5.1. */
76
+ .chip-list__form {
77
+ display: inline-flex;
78
+ margin: 0;
79
+ padding: 0;
80
+ }
81
+
68
82
  .chip-list__delete {
69
83
  display: inline-flex;
70
84
  align-items: center;
@@ -34,6 +34,17 @@
34
34
  }
35
35
  .panel__title-row .panel__title { margin-bottom: 0; }
36
36
 
37
+ /* Likt .panel__title-row men med baseline-alignment (titel + meta-text
38
+ i hoger kolumn pa olika fontsizes ska vila pa samma baslinje).
39
+ v3.5.1. */
40
+ .panel__header-row {
41
+ display: flex;
42
+ justify-content: space-between;
43
+ align-items: baseline;
44
+ margin-bottom: var(--space-12);
45
+ }
46
+ .panel__header-row .panel__title { margin-bottom: 0; }
47
+
37
48
  .panel__title-meta {
38
49
  font-size: var(--fs-12);
39
50
  color: var(--text-muted);
@@ -72,6 +83,23 @@
72
83
  color: var(--warning);
73
84
  }
74
85
 
86
+ /* Pill inom attention-panel (t.ex. count "2 att atgarda"). Anvander
87
+ warning-tokens for konsekvens med .panel--attention. v3.5.1. */
88
+ .panel--attention__pill {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ height: 24px;
92
+ padding: 0 var(--space-10);
93
+ background: var(--warning-dim);
94
+ color: var(--warning);
95
+ font-size: var(--fs-12);
96
+ font-weight: var(--fw-medium);
97
+ border: 1px solid var(--warning-border);
98
+ border-radius: var(--radius-full);
99
+ white-space: nowrap;
100
+ font-variant-numeric: tabular-nums;
101
+ }
102
+
75
103
  .panel__step-row {
76
104
  display: flex;
77
105
  align-items: center;
@@ -136,6 +136,13 @@
136
136
  white-space: nowrap;
137
137
  }
138
138
 
139
+ /* Wrapper for <form>-element som hostar setting-rows (single-row-form
140
+ med per-field POST). Default UA-margin nollstalls. v3.5.1. */
141
+ .setting-row__form {
142
+ display: block;
143
+ margin: 0;
144
+ }
145
+
139
146
 
140
147
  /* ================================================================
141
148
  ==== TOGGLE (iOS-stil)
@@ -0,0 +1,399 @@
1
+ /* ================================================================
2
+ components/sheet-content.css
3
+ Sheet-innehallets typografi + form-monster. Kompletterar
4
+ .sheet/.sheet-handle/.sheet-body/.sheet-divider i overlay.css.
5
+
6
+ Generiska BEM-klasser - inga domain-namn (cat → item, tx →
7
+ description, excluded → subform). Apparna binder bara data
8
+ (kategori-tokens etc) via app-domain-CSS.
9
+
10
+ Strukturer:
11
+ - Title + description (sektionsrubrik + kontext-text)
12
+ - Form (vertikal stack med fields)
13
+ - Field (label + input-wrap eller standalone-input + hint)
14
+ - Items grid (selectable items 2-kol)
15
+ - Actions (multi-step button-grid)
16
+ - Toggle (boolean-field row)
17
+ - Subform (sub-block med border-top)
18
+ - Action-buttons (save/delete fixed sizes)
19
+ ================================================================ */
20
+
21
+
22
+ /* ================================================================
23
+ ==== TITLE + DESCRIPTION
24
+ ================================================================ */
25
+ .sheet__title {
26
+ font-size: var(--fs-13);
27
+ font-weight: var(--fw-medium);
28
+ letter-spacing: 0.04em;
29
+ text-transform: uppercase;
30
+ color: var(--text-muted);
31
+ margin: 0 0 var(--space-8);
32
+ }
33
+
34
+ .sheet__description {
35
+ font-size: var(--fs-14);
36
+ color: var(--text-default);
37
+ margin: 0 0 var(--space-18);
38
+ word-break: break-word;
39
+ }
40
+
41
+
42
+ /* ================================================================
43
+ ==== FORM-STRUKTUR
44
+ .sheet__form ar vertikal flex-stack med 16px gap mellan fields.
45
+ .sheet__form--inline tar bort margin sa <form> kan vara inline.
46
+ .sheet__field ar per-field-container (label + input + hint).
47
+ ================================================================ */
48
+ .sheet__form {
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: var(--space-16);
52
+ margin: 0;
53
+ }
54
+
55
+ .sheet__form--inline {
56
+ margin: 0;
57
+ }
58
+ .sheet__form--inline button {
59
+ width: 100%;
60
+ }
61
+
62
+ .sheet__field {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: var(--space-6);
66
+ }
67
+
68
+ .sheet__field-label {
69
+ font-size: var(--fs-12);
70
+ font-weight: var(--fw-medium);
71
+ color: var(--text-muted);
72
+ margin: 0;
73
+ }
74
+
75
+ .sheet__field-label--upper {
76
+ font-size: var(--fs-11);
77
+ letter-spacing: 0.06em;
78
+ text-transform: uppercase;
79
+ margin-bottom: var(--space-6);
80
+ }
81
+
82
+
83
+ /* ================================================================
84
+ ==== INPUTS
85
+ Input-wrap (med focus-within-state) ger sprint-65-ramad input.
86
+ Standalone-input ar fristaaende variant utan wrapper.
87
+ ================================================================ */
88
+ .sheet__input-wrap {
89
+ position: relative;
90
+ display: flex;
91
+ align-items: center;
92
+ background: var(--surface-sunken);
93
+ border: 1px solid var(--border-default);
94
+ border-radius: var(--radius-12);
95
+ height: 48px;
96
+ transition: background var(--dur-fast) var(--ease-spring-snappy);
97
+ }
98
+
99
+ .sheet__input-wrap:focus-within {
100
+ outline: 2px solid var(--accent-9);
101
+ outline-offset: -1px;
102
+ background: var(--surface-default);
103
+ border-color: transparent;
104
+ }
105
+
106
+ .sheet__input {
107
+ flex: 1;
108
+ min-width: 0;
109
+ height: 100%;
110
+ padding: 0 var(--space-14);
111
+ background: transparent;
112
+ border: 0;
113
+ color: var(--text-default);
114
+ font-size: var(--fs-17);
115
+ font-family: inherit;
116
+ outline: none;
117
+ }
118
+ .sheet__input::placeholder {
119
+ color: var(--text-disabled);
120
+ }
121
+
122
+ .sheet__input--select {
123
+ appearance: none;
124
+ -webkit-appearance: none;
125
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
126
+ background-repeat: no-repeat;
127
+ background-position: right var(--space-12) center;
128
+ background-size: 14px;
129
+ padding-right: var(--space-32);
130
+ color: var(--text-muted);
131
+ }
132
+
133
+ .sheet__input--standalone {
134
+ display: block;
135
+ width: 100%;
136
+ padding: var(--space-12) var(--space-14);
137
+ background: var(--surface-sunken);
138
+ border: 1px solid var(--border-default);
139
+ border-radius: var(--radius-12);
140
+ color: var(--text-default);
141
+ font-size: var(--fs-17);
142
+ font-family: inherit;
143
+ outline: none;
144
+ margin-top: var(--space-6);
145
+ transition: background var(--dur-fast) var(--ease-spring-snappy);
146
+ }
147
+
148
+ .sheet__input--standalone:focus {
149
+ outline: 2px solid var(--accent-9);
150
+ outline-offset: -1px;
151
+ background: var(--surface-default);
152
+ border-color: transparent;
153
+ }
154
+
155
+ .sheet__suffix {
156
+ padding: 0 var(--space-14) 0 0;
157
+ font-size: var(--fs-14);
158
+ font-weight: var(--fw-medium);
159
+ color: var(--text-muted);
160
+ flex-shrink: 0;
161
+ }
162
+
163
+
164
+ /* ================================================================
165
+ ==== HINT-TEXT
166
+ .sheet__hint - default 12px text-muted med margin
167
+ .sheet__hint--compact - 11px utan margin (sprint 65 in-form-hint)
168
+ .is-error - rod variant for validation-fel
169
+ ================================================================ */
170
+ .sheet__hint {
171
+ font-size: var(--fs-12);
172
+ color: var(--text-muted);
173
+ margin: var(--space-8) 0 var(--space-16);
174
+ }
175
+ .sheet__hint em {
176
+ font-style: italic;
177
+ color: var(--text-subtle);
178
+ }
179
+
180
+ .sheet__hint--compact {
181
+ font-size: var(--fs-11);
182
+ margin: 0;
183
+ line-height: var(--lh-snug);
184
+ }
185
+
186
+ .sheet__hint.is-error,
187
+ .sheet__hint--compact.is-error {
188
+ color: var(--accent-danger);
189
+ }
190
+
191
+
192
+ /* ================================================================
193
+ ==== ITEMS GRID (selectable items 2-kol)
194
+ Generic monster for kategori-val, alternativ-val osv. Apparna
195
+ binder data via egna domain-klasser pa item-elementet.
196
+ ================================================================ */
197
+ .sheet__item-grid {
198
+ display: grid;
199
+ grid-template-columns: repeat(2, 1fr);
200
+ gap: var(--space-8);
201
+ }
202
+
203
+ .sheet__item {
204
+ display: block;
205
+ width: 100%;
206
+ padding: var(--space-12) var(--space-14);
207
+ background: var(--surface-default);
208
+ border: 1px solid var(--border-default);
209
+ border-radius: var(--radius-14);
210
+ color: var(--text-default);
211
+ font-size: var(--fs-14);
212
+ font-weight: var(--fw-medium);
213
+ font-family: inherit;
214
+ cursor: pointer;
215
+ text-align: center;
216
+ transition: background var(--dur-fast) var(--ease-spring-snappy);
217
+ -webkit-tap-highlight-color: transparent;
218
+ touch-action: manipulation;
219
+ }
220
+
221
+ @media (hover: hover) and (pointer: fine) {
222
+ .sheet__item:hover {
223
+ background: var(--surface-hover);
224
+ }
225
+ }
226
+
227
+ .sheet__item:focus-visible {
228
+ outline: 2px solid var(--border-focus);
229
+ outline-offset: 2px;
230
+ }
231
+
232
+ .sheet__item:active {
233
+ transform: scale(0.985);
234
+ transition: transform 80ms var(--ease-spring-snappy);
235
+ }
236
+
237
+ .sheet__item--primary {
238
+ background: var(--accent-9);
239
+ color: var(--text-on-accent);
240
+ border-color: var(--accent-9);
241
+ }
242
+ @media (hover: hover) and (pointer: fine) {
243
+ .sheet__item--primary:hover {
244
+ background: var(--accent-10);
245
+ border-color: var(--accent-10);
246
+ }
247
+ }
248
+
249
+ .sheet__item--reset {
250
+ grid-column: 1 / -1;
251
+ margin-top: var(--space-6);
252
+ background: transparent;
253
+ color: var(--text-subtle);
254
+ border-style: dashed;
255
+ }
256
+
257
+ .sheet__item--back {
258
+ grid-column: 1 / -1;
259
+ margin-bottom: var(--space-4);
260
+ background: transparent;
261
+ color: var(--text-subtle);
262
+ border-style: dashed;
263
+ }
264
+
265
+
266
+ /* ================================================================
267
+ ==== MULTI-STEP ACTIONS-GRID
268
+ 2-kol button-grid for steg-N actions (t.ex. Tillbaka + Spara).
269
+ ================================================================ */
270
+ .sheet__actions {
271
+ display: grid;
272
+ grid-template-columns: 1fr 1fr;
273
+ gap: var(--space-8);
274
+ }
275
+
276
+
277
+ /* ================================================================
278
+ ==== CONFIRMATION
279
+ "Du valde X"-text fore step-2-actions.
280
+ ================================================================ */
281
+ .sheet__confirm {
282
+ font-size: var(--fs-14);
283
+ color: var(--text-subtle);
284
+ margin: 0 0 var(--space-14);
285
+ }
286
+ .sheet__confirm strong {
287
+ color: var(--text-default);
288
+ font-weight: var(--fw-medium);
289
+ }
290
+
291
+
292
+ /* ================================================================
293
+ ==== TOGGLE (boolean field)
294
+ Row-layout for label + native checkbox/toggle.
295
+ ================================================================ */
296
+ .sheet__toggle {
297
+ display: flex;
298
+ align-items: center;
299
+ justify-content: space-between;
300
+ gap: var(--space-10);
301
+ cursor: pointer;
302
+ padding: var(--space-6) 0;
303
+ }
304
+
305
+ .sheet__toggle-label {
306
+ font-size: var(--fs-14);
307
+ font-weight: var(--fw-medium);
308
+ color: var(--text-default);
309
+ }
310
+
311
+ .sheet__toggle-input {
312
+ width: 22px;
313
+ height: 22px;
314
+ accent-color: var(--accent-9);
315
+ cursor: pointer;
316
+ flex-shrink: 0;
317
+ }
318
+
319
+
320
+ /* ================================================================
321
+ ==== SUBFORM (sub-block med border-top divider)
322
+ Anvands for excluded-toggle-form etc i sheets med flera
323
+ logiska delar.
324
+ ================================================================ */
325
+ .sheet__subform {
326
+ margin: var(--space-18) 0 0;
327
+ padding-top: var(--space-14);
328
+ border-top: 1px solid var(--border-default);
329
+ }
330
+
331
+
332
+ /* ================================================================
333
+ ==== ACTION-BUTTONS (fixed sizes for sheet-context)
334
+ .sheet__btn--save - primary 48px full-bredd accent-9
335
+ .sheet__btn--delete - sekundar destructive 44px transparent
336
+ Tightly coupled to sheet-form-sizing - inte generiska .btn.
337
+ ================================================================ */
338
+ .sheet__btn--save {
339
+ display: block;
340
+ width: 100%;
341
+ height: 48px;
342
+ margin: var(--space-8) 0 0;
343
+ background: var(--accent-9);
344
+ color: var(--text-on-accent);
345
+ border: 0;
346
+ border-radius: var(--radius-14);
347
+ font-family: inherit;
348
+ font-size: var(--fs-15);
349
+ font-weight: var(--fw-medium);
350
+ cursor: pointer;
351
+ transition: background var(--dur-fast) var(--ease-spring-snappy);
352
+ -webkit-tap-highlight-color: transparent;
353
+ touch-action: manipulation;
354
+ }
355
+
356
+ .sheet__btn--save:disabled {
357
+ opacity: 0.5;
358
+ cursor: not-allowed;
359
+ }
360
+
361
+ @media (hover: hover) and (pointer: fine) {
362
+ .sheet__btn--save:not(:disabled):hover {
363
+ background: var(--accent-10);
364
+ }
365
+ }
366
+
367
+ .sheet__btn--save:focus-visible {
368
+ outline: 2px solid var(--border-focus);
369
+ outline-offset: 2px;
370
+ }
371
+
372
+ .sheet__btn--delete {
373
+ display: block;
374
+ width: 100%;
375
+ height: 44px;
376
+ margin: var(--space-8) 0 0;
377
+ background: transparent;
378
+ color: var(--accent-danger);
379
+ border: 0;
380
+ border-radius: var(--radius-14);
381
+ font-family: inherit;
382
+ font-size: var(--fs-14);
383
+ font-weight: var(--fw-medium);
384
+ cursor: pointer;
385
+ transition: background var(--dur-fast) var(--ease-spring-snappy);
386
+ -webkit-tap-highlight-color: transparent;
387
+ touch-action: manipulation;
388
+ }
389
+
390
+ @media (hover: hover) and (pointer: fine) {
391
+ .sheet__btn--delete:hover {
392
+ background: var(--accent-danger-dim);
393
+ }
394
+ }
395
+
396
+ .sheet__btn--delete:focus-visible {
397
+ outline: 2px solid var(--border-focus);
398
+ outline-offset: 2px;
399
+ }
package/css/index.css CHANGED
@@ -62,3 +62,6 @@
62
62
  /* v3.1.0 - generic kategori-monster (token-binding via app-domain-CSS) */
63
63
  @import './components/colored-row.css';
64
64
  @import './components/colored-bar.css';
65
+
66
+ /* v3.5.1 - sheet-content (sprint-65 form-monster, generiska BEM-namn) */
67
+ @import './components/sheet-content.css';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@klodd/ds",
3
- "version": "3.4.5",
4
- "description": "Klodd Design System - shared tokens, typography, components and JS for Jubb, Ekonom, and future apps. v3.4.5 tar bort --plum-2-5-token (oanvänd sedan v3.4.2 när surface-raised flyttade till plum-2).",
3
+ "version": "3.5.1",
4
+ "description": "Klodd Design System - shared tokens, typography, components and JS for Jubb, Ekonom, and future apps. v3.5.1 (2026-05-08): nya foreman-typografi-klasser (.text-caption/-tiny/-num/-num--display/-positive/-warning), nya sheet-content-komponent (24 BEM-klasser för Sprint-65-pattern), nya btn-modifiers (--block, --add), och nya elements i panel.css (.panel__header-row, .panel--attention__pill), setting-row.css (.setting-row__form), chip.css (.chip-list__text, .chip-list__form). Sektion C av sprint-C-migration (paket-utvidgning, ej template-migration).",
5
5
  "main": "css/index.css",
6
6
  "bin": {
7
7
  "klodd-ds": "./bin/klodd-ds.js"
@@ -223,16 +223,21 @@ Varje interaktiv komponent SKA ha:
223
223
  - Linear, Vercel och liknande modern SaaS anvander samma stack
224
224
 
225
225
 
226
- ## 12. Audit-aliaser halls levande tills hela migration genomford
226
+ ## 12. Bara nya semantic token-namn
227
227
 
228
228
  **Hur man foljer:**
229
- - Gamla token-namn (--text-primary, --bg-base, --accent-dim) finns kvar
230
- i 10-semantic.css's "LEGACY ALIASES"-block
231
- - Komponent-CSS som migrerats anvander nya semantic-namn
232
- (--text-default, --surface-page, --accent-a3)
233
- - Tar INTE bort legacy-aliaser forrans alla call-sites migrerats
229
+ - Anvand bara nya semantic-namn: `--text-default/-subtle/-muted/-disabled`,
230
+ `--surface-page/-default/-raised/-overlay/-hover/-active/-sunken`,
231
+ `--accent-soft/-9/-10/-text/-a10/-a20/-a40` osv.
232
+ - Gamla namn (--text-primary, --bg-base, --accent-dim, --fw-semibold,
233
+ --radius-sm/md/lg/xl/2xl/3xl/4xl) ar **borttagna** i v3.5.0
234
+ (2026-05-08). Ingen alias-bro langre.
234
235
 
235
236
  **Varfor:**
236
- - Migrations-bro: app-CSS som inte uppdaterats fortsatter funka
237
- - Big-bang-migrationer ar riskabla - per-template-migration ar saker
238
- - Open punkter dokumenterade i `05-open-decisions/`
237
+ - En standard, en mental modell. Inga "vilket namn ska jag anvanda?"-
238
+ fragor i koden.
239
+ - Komponenter som referensade gamla namn migrerades i 2026-05-07/08-
240
+ sprintarna innan aliasen raderades. Inga kvarstaende call-sites.
241
+
242
+ Migrations-historiken finns dokumenterad i ADR 0008 (CLOSED) och
243
+ ADR 0009 (locked).
@@ -0,0 +1,94 @@
1
+ # 0009 - Inga flat-fil-aggregat av komponenter (`ds/components.css` förbjudet)
2
+
3
+ ## Status
4
+ Locked.
5
+
6
+ ## Context
7
+
8
+ Innan 2026-05-08 hade både Jubb och Ekonom en flat-fil
9
+ `app/static/css/ds/components.css` per repo. Filen aggregerade
10
+ paketets komponentstyling med app-specifik domain-CSS i en stor
11
+ samlingsfil.
12
+
13
+ Filen var ett resultat av att designsystemet började som en kopierad
14
+ samling klasser **innan** paketet flyttades till `@klodd/ds`. När
15
+ paketet bröts ut till en egen npm-modul med `components/<x>.css`-
16
+ filer per komponent fanns ingen tvingande mekanism att avveckla
17
+ flat-filen.
18
+
19
+ Filen växte över tid till en parallellprodukt mot paketets
20
+ `components/`-mapp:
21
+
22
+ - Dubbel-styling: samma selector definierades på två platser
23
+ - Drift: paketet uppdaterades men flat-filen släpade efter
24
+ - Underhållsbörda: ändring krävde uppdatering i två repos + paketet
25
+ - Otydlig ansvarsgräns: vad är paket-styling, vad är domain?
26
+
27
+ ## Decision
28
+
29
+ `ds/components.css` (eller motsvarande aggregat-fil) i appar är
30
+ **förbjuden**. Appar konsumerar paketets `components/`-mapp direkt
31
+ via `@import url("ds/components/<x>.css")` i `main.css` (eller
32
+ motsvarande bundle-fil).
33
+
34
+ App-specifik domain-CSS (kategorisystem, score-pills, app-unika
35
+ element) lever i en tydligt namngiven domain-fil:
36
+
37
+ - Jubb: `app/static/css/jubb-theme.css`
38
+ - Ekonom: `app/static/css/ds/ekonom-domain.css`
39
+
40
+ ## Consequences
41
+
42
+ **Bra:**
43
+ - Paketet är enda källan för komponent-styling
44
+ - Drift mellan paket och app är omöjlig
45
+ - Bumpa paketversion = automatiskt nya stilar i alla konsumenter
46
+ - Tydlig ansvarsgräns: paket = komponenter, domain-fil = app-spec
47
+
48
+ **Trade-off:**
49
+ - `main.css` får fler `@import`-rader (en per komponent). Inte ett
50
+ problem - browsern parallelliserar. Service Worker pre-cachar dem.
51
+ - Ny komponent kräver `@import` i alla konsumerande appars `main.css`.
52
+ Acceptabelt - paketet listar komponenter i README.
53
+
54
+ ## Migration (genomförd 2026-05-08)
55
+
56
+ Båda repos genomgick **selector-för-selector-radering**, inte big-bang:
57
+
58
+ 1. Klassificera varje sektion: paket-duplicat eller app-domain?
59
+ 2. Paket-duplicat raderas direkt
60
+ 3. App-domain flyttas till domain-fil
61
+ 4. När filen är tom raderas den helt
62
+ 5. `main.css` importerar paketet direkt
63
+
64
+ **Big-bang-försök rollbackades direkt** vid första testet - massiv
65
+ visuell regression (bottom-nav-blackouts, hub-card stylelöshet).
66
+ Selector-för-selector var rätt val för att hålla appen stabil under
67
+ migrationen.
68
+
69
+ Verifiering på prod via Chrome MCP:
70
+ - Jubb: 21 paket-imports, panel.css applierad, bottom-nav 20px
71
+ - Ekonom: 25 nested @imports (CSSOM-rekursion), components.css = 404,
72
+ panel.bg = rgb(32,19,32) Plum-2, hero rendererar
73
+
74
+ **Tester:** 429 pass (Jubb) + 636 pass (Ekonom), oförändrat.
75
+
76
+ ## Framöver
77
+
78
+ Ny app som konsumerar paketet:
79
+ 1. Skapa egen domain-fil: `app/static/css/<app>-domain.css`
80
+ 2. Importera paketet direkt i `main.css` eller bundle-fil
81
+ 3. Använd `npx @klodd/ds precache` för att generera SW PRECACHE_URLS
82
+ 4. Bumpa SW-version vid varje paketuppdatering
83
+
84
+ **Aldrig** skapa en flat-fil-aggregat à la `ds/components.css`.
85
+
86
+ ## References
87
+
88
+ - Ekonom commit: `8a0611e` (refactor(ds): ds/components.css raderad)
89
+ - Jubb commits: `cf334b5` (Block 1) + `6e0710d` (Block 2) + `d8a6c12`
90
+ (Block 3)
91
+ - Ekonom STATUS.md 2026-05-08
92
+ - Jubb STATUS.md 2026-05-08
93
+ - ADR 0001 (three-layer-token-architecture)
94
+ - ADR 0008 (npm-package-distribution)
@@ -1,6 +1,23 @@
1
1
  # 0008 - Legacy token-migration och `ds/components.css`-radering - v4.0.0
2
2
 
3
3
  ## Status
4
+ **CLOSED 2026-05-08.** Bagge stegen genomforda. Se ADR 0009 (locked)
5
+ "Inga flat-fil-aggregat av komponenter" som ar den varande regeln
6
+ framover.
7
+
8
+ **Resultat:**
9
+ - LEGACY ALIASES-sektionen raderad ur `10-semantic.css` i paket v3.5.0
10
+ - `ds/components.css` raderad ur Jubb (commits cf334b5 + 6e0710d +
11
+ d8a6c12) och Ekonom (commit 8a0611e)
12
+ - Bagge appar kor 100% paket-driven design via `@klodd/ds`-komponenter
13
+ + per-app domain-fil (`jubb-theme.css` resp. `ekonom-domain.css`)
14
+ - 429 pass (Jubb) + 636 pass (Ekonom), oforandrat
15
+
16
+ Originaltexten bevarad nedan for historik.
17
+
18
+ ---
19
+
20
+ ## Original status (innan stangning)
4
21
  Pending. Trigger: innan tredje app borjar anvanda `@klodd/ds`.
5
22
 
6
23
  ## Context