@klodd/ds 4.1.0 → 4.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klodd/ds",
3
- "version": "4.1.0",
3
+ "version": "4.2.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
+ "stylelint-plugin/",
13
14
  "SKILL.md",
14
15
  "references/"
15
16
  ],
@@ -0,0 +1,192 @@
1
+ /* @klodd/ds stylelint-plugin
2
+ *
3
+ * Custom design-rule-validation som stylelint-config-standard inte
4
+ * tacker. Tre regler, alla "klodd/"-prefixade:
5
+ *
6
+ * - klodd/no-forbidden-radius:
7
+ * border-radius maste vara en av {var(--radius-4), var(--radius-14),
8
+ * 50%, var(--radius-full), 9999px} per ADR 0013 + DESIGN-LANGUAGE
9
+ * sektion 1 (BR-skala 4 varden).
10
+ *
11
+ * - klodd/no-forbidden-shadow:
12
+ * box-shadow maste vara `none`, `inset ...` (focus-rings),
13
+ * `0 0 0 Npx var(--accent-aN)` (focus-rings via spread), eller
14
+ * `var(--shadow-card)` per regel 10 + DESIGN-LANGUAGE sektion 2.
15
+ *
16
+ * - klodd/no-forbidden-padding:
17
+ * padding-varden maste anvanda var(--space-N) dar N matchar en
18
+ * existerande token i 00-primitives.css. Hardkodade px/em/%-
19
+ * varden (utom 0/auto) flaggas. Per-komponent-typ-disciplin
20
+ * (list-row 10/0, banner 14/16 etc) ar fortfarande mansklig
21
+ * review mot ADR 0017-tabellen - plugin validerar bara token-
22
+ * existens, inte semantisk val per komponent.
23
+ *
24
+ * Konfiguration i app-repos:
25
+ * .stylelintrc.json:
26
+ * {
27
+ * "plugins": ["@klodd/ds/stylelint-plugin"],
28
+ * "rules": {
29
+ * "klodd/no-forbidden-radius": true,
30
+ * "klodd/no-forbidden-shadow": true,
31
+ * "klodd/no-forbidden-padding": true
32
+ * }
33
+ * }
34
+ */
35
+
36
+ const stylelint = require('stylelint');
37
+
38
+ const ALLOWED_RADIUS = new Set([
39
+ '0',
40
+ '50%',
41
+ '9999px',
42
+ 'var(--radius-4)',
43
+ 'var(--radius-14)',
44
+ 'var(--radius-full)',
45
+ 'inherit',
46
+ 'initial',
47
+ 'unset',
48
+ ]);
49
+
50
+ // Tillatna --space-N tokens enligt 00-primitives.css.
51
+ // Komponent-padding bor halla sig inom 4-24-skalan per ADR 0017 men
52
+ // mini-elements (badge, pill-nav) far anvanda --space-2 och
53
+ // layout-section-padding far anvanda --space-28/-32/-40+. Pluginet
54
+ // validerar bara att tokenen EXISTERAR - per-komponent-typ-disciplin
55
+ // ar fortfarande mansklig review mot ADR 0017-tabellen.
56
+ const ALLOWED_PADDING_TOKENS = new Set([
57
+ '2', '4', '6', '8', '10', '12', '14', '16', '18', '20',
58
+ '22', '24', '28', '32', '40', '48', '56', '64', '80',
59
+ ]);
60
+
61
+ const SHADOW_NONE = /^none$/i;
62
+ const SHADOW_INSET = /\binset\b/i;
63
+ const SHADOW_FOCUS_RING = /^0\s+0\s+0\s+\d+px\s+(var\(--accent-a\d+\)|color-mix\(.+\))/i;
64
+ const SHADOW_CARD_TOKEN = /^var\(--shadow-card\)$/i;
65
+
66
+ const RADIUS_RULE = 'klodd/no-forbidden-radius';
67
+ const SHADOW_RULE = 'klodd/no-forbidden-shadow';
68
+ const PADDING_RULE = 'klodd/no-forbidden-padding';
69
+
70
+ const messages = {
71
+ radius: stylelint.utils.ruleMessages(RADIUS_RULE, {
72
+ forbidden: (value) =>
73
+ `Forbjudet border-radius "${value}" - anvand en av: var(--radius-4) | var(--radius-14) | 50% | var(--radius-full) (ADR 0013 + DESIGN-LANGUAGE 1)`,
74
+ }),
75
+ shadow: stylelint.utils.ruleMessages(SHADOW_RULE, {
76
+ forbidden: (value) =>
77
+ `Forbjudet box-shadow "${value}" - tillatet: none | inset focus-rings | var(--shadow-card) (DESIGN-LANGUAGE 2 + regel 10)`,
78
+ }),
79
+ padding: stylelint.utils.ruleMessages(PADDING_RULE, {
80
+ forbidden: (value) =>
81
+ `Forbjudet padding-varde "${value}" - anvand var(--space-N) (existerande token i 00-primitives.css). Hardkodade px-varden inte tillatna.`,
82
+ }),
83
+ };
84
+
85
+ function checkRadius (root, result, primary) {
86
+ if (!primary) return;
87
+ root.walkDecls(/^border(-(top|right|bottom|left))?-?radius$/, (decl) => {
88
+ const values = decl.value.split(/\s+/);
89
+ values.forEach((v) => {
90
+ const normalized = v.trim();
91
+ if (!normalized) return;
92
+ if (ALLOWED_RADIUS.has(normalized)) return;
93
+ stylelint.utils.report({
94
+ ruleName: RADIUS_RULE,
95
+ result,
96
+ node: decl,
97
+ message: messages.radius.forbidden(normalized),
98
+ });
99
+ });
100
+ });
101
+ }
102
+
103
+ function checkShadow (root, result, primary) {
104
+ if (!primary) return;
105
+ root.walkDecls(/^box-shadow$/, (decl) => {
106
+ const value = decl.value.trim();
107
+ if (SHADOW_NONE.test(value)) return;
108
+ if (SHADOW_INSET.test(value)) return;
109
+ if (SHADOW_FOCUS_RING.test(value)) return;
110
+ if (SHADOW_CARD_TOKEN.test(value)) return;
111
+ stylelint.utils.report({
112
+ ruleName: SHADOW_RULE,
113
+ result,
114
+ node: decl,
115
+ message: messages.shadow.forbidden(value),
116
+ });
117
+ });
118
+ }
119
+
120
+ function checkPadding (root, result, primary) {
121
+ if (!primary) return;
122
+ root.walkDecls(/^padding(-(top|right|bottom|left))?$/, (decl) => {
123
+ const values = decl.value.split(/\s+/);
124
+ values.forEach((v) => {
125
+ const normalized = v.trim();
126
+ if (!normalized) return;
127
+ if (normalized === '0' || normalized === 'auto' || normalized === 'inherit') return;
128
+ // var(--space-N)?
129
+ const tokenMatch = normalized.match(/^var\(--space-(\d+)\)$/);
130
+ if (tokenMatch) {
131
+ if (ALLOWED_PADDING_TOKENS.has(tokenMatch[1])) return;
132
+ stylelint.utils.report({
133
+ ruleName: PADDING_RULE,
134
+ result,
135
+ node: decl,
136
+ message: messages.padding.forbidden(normalized),
137
+ });
138
+ return;
139
+ }
140
+ // Hardkodad px / em / % - flagga
141
+ if (/^[\d.]+(px|em|rem|%)$/.test(normalized)) {
142
+ stylelint.utils.report({
143
+ ruleName: PADDING_RULE,
144
+ result,
145
+ node: decl,
146
+ message: messages.padding.forbidden(normalized),
147
+ });
148
+ }
149
+ });
150
+ });
151
+ }
152
+
153
+ const radiusPlugin = stylelint.createPlugin(RADIUS_RULE, (primary) => {
154
+ return (root, result) => {
155
+ const validOptions = stylelint.utils.validateOptions(result, RADIUS_RULE, {
156
+ actual: primary,
157
+ possible: [true, false],
158
+ });
159
+ if (!validOptions) return;
160
+ checkRadius(root, result, primary);
161
+ };
162
+ });
163
+ radiusPlugin.ruleName = RADIUS_RULE;
164
+ radiusPlugin.messages = messages.radius;
165
+
166
+ const shadowPlugin = stylelint.createPlugin(SHADOW_RULE, (primary) => {
167
+ return (root, result) => {
168
+ const validOptions = stylelint.utils.validateOptions(result, SHADOW_RULE, {
169
+ actual: primary,
170
+ possible: [true, false],
171
+ });
172
+ if (!validOptions) return;
173
+ checkShadow(root, result, primary);
174
+ };
175
+ });
176
+ shadowPlugin.ruleName = SHADOW_RULE;
177
+ shadowPlugin.messages = messages.shadow;
178
+
179
+ const paddingPlugin = stylelint.createPlugin(PADDING_RULE, (primary) => {
180
+ return (root, result) => {
181
+ const validOptions = stylelint.utils.validateOptions(result, PADDING_RULE, {
182
+ actual: primary,
183
+ possible: [true, false],
184
+ });
185
+ if (!validOptions) return;
186
+ checkPadding(root, result, primary);
187
+ };
188
+ });
189
+ paddingPlugin.ruleName = PADDING_RULE;
190
+ paddingPlugin.messages = messages.padding;
191
+
192
+ module.exports = [radiusPlugin, shadowPlugin, paddingPlugin];