@estiva-app/ui 0.43.0 → 0.44.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.
@@ -18,6 +18,13 @@ import type { Rule } from 'eslint';
18
18
  * file and scrolled in another. A `contentClassName` with `min-h-full` or
19
19
  * `h-full` gives the state its room, and passes.
20
20
  *
21
+ * **A box that places it** (UIG-42). A plain box holding only a page-scope
22
+ * `EmptyState` and centring or padding it does the part's own job, and a box
23
+ * like that pinned the starter's home page 64px from the top (UIG-10). A box
24
+ * that is only the flex column the state asks for passes, and so does any box
25
+ * round a `section` state, which takes its inset from the box (Katerina, 14
26
+ * September).
27
+ *
21
28
  * Not judged: the package's `EmptyState` and a part's own lines (a
22
29
  * `FieldLine`, a palette's empty row) — a part is already a part.
23
30
  */
@@ -1 +1 @@
1
- {"version":3,"file":"no-handmade-empty-state.d.ts","sourceRoot":"","sources":["../../src/eslint/no-handmade-empty-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAuPlC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oBAAoB,EAAE,IAAI,CAAC,UAyEvC,CAAA"}
1
+ {"version":3,"file":"no-handmade-empty-state.d.ts","sourceRoot":"","sources":["../../src/eslint/no-handmade-empty-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAiQlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,oBAAoB,EAAE,IAAI,CAAC,UA4FvC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-checks.d.ts","sourceRoot":"","sources":["../../src/gates/app-checks.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAA0B,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE/E,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAA;IACZ,8GAA8G;IAC9G,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IACvC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,+GAA+G;IAC/G,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsBpD,CAAA;AAaD,wBAAgB,SAAS,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,GAAS,EAAE,IAAI,EAAE,KAAsC,EAAE,YAA8D,EAAE,OAAmB,EAAE,EAAE,eAAe,GAAG,UAAU,EAAE,CAuOzN"}
1
+ {"version":3,"file":"app-checks.d.ts","sourceRoot":"","sources":["../../src/gates/app-checks.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAA0B,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE/E,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAA;IACZ,8GAA8G;IAC9G,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IACvC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,+GAA+G;IAC/G,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAuBpD,CAAA;AAaD,wBAAgB,SAAS,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,GAAS,EAAE,IAAI,EAAE,KAAsC,EAAE,YAA8D,EAAE,OAAmB,EAAE,EAAE,eAAe,GAAG,UAAU,EAAE,CA6OzN"}
@@ -10,6 +10,63 @@ import betterTailwindcss from "eslint-plugin-better-tailwindcss";
10
10
  import { getDefaultSelectors } from "eslint-plugin-better-tailwindcss/defaults";
11
11
  import { parser as typescriptParser, plugin as typescriptPlugin } from "typescript-eslint";
12
12
 
13
+ // src/gates/token-joined.ts
14
+ var isOneSpace = (node) => node?.type === "Literal" && node.value === " ";
15
+ function joinedList(call) {
16
+ const callee = call.callee;
17
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.name !== "join") return void 0;
18
+ if (call.arguments.length !== 1 || !isOneSpace(call.arguments[0])) return void 0;
19
+ let list = callee.object;
20
+ while (list.type === "CallExpression" && list.callee.type === "MemberExpression" && list.callee.property.name === "filter") {
21
+ list = list.callee.object;
22
+ }
23
+ return list.type === "ArrayExpression" ? list : void 0;
24
+ }
25
+ function stringsIn(node, out = []) {
26
+ if (!node) return out;
27
+ if (node.type === "Literal" && typeof node.value === "string") out.push({ node, text: node.value });
28
+ else if (node.type === "TemplateLiteral") for (const quasi of node.quasis) out.push({ node: quasi, text: String(quasi.value.cooked ?? "") });
29
+ else if (node.type === "ConditionalExpression") stringsIn(node.consequent, out), stringsIn(node.alternate, out);
30
+ else if (node.type === "LogicalExpression") {
31
+ if (node.operator !== "&&") stringsIn(node.left, out);
32
+ stringsIn(node.right, out);
33
+ } else if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") stringsIn(node.expression, out);
34
+ return out;
35
+ }
36
+ var joinedListRule = {
37
+ meta: {
38
+ type: "problem",
39
+ docs: { description: 'The token contract, inside a class list joined with .join(" ")' },
40
+ schema: [
41
+ {
42
+ type: "object",
43
+ properties: { restrict: { type: "array", items: { type: "object", properties: { pattern: { type: "string" }, message: { type: "string" } }, required: ["pattern", "message"] } } },
44
+ required: ["restrict"],
45
+ additionalProperties: false
46
+ }
47
+ ]
48
+ },
49
+ create(context) {
50
+ const { restrict } = context.options[0];
51
+ const rules = restrict.map(({ pattern, message }) => ({ pattern: new RegExp(pattern), message }));
52
+ return {
53
+ CallExpression(ruleNode) {
54
+ const list = joinedList(ruleNode);
55
+ if (!list) return;
56
+ for (const item of list.elements) {
57
+ for (const { node, text } of stringsIn(item)) {
58
+ for (const name of text.split(/\s+/)) {
59
+ const rule = name && rules.find(({ pattern }) => pattern.test(name));
60
+ if (rule) context.report({ node, message: rule.message.replaceAll("$0", name) });
61
+ }
62
+ }
63
+ }
64
+ }
65
+ };
66
+ }
67
+ };
68
+ var tokenJoinedPlugin = { rules: { classes: joinedListRule, values: joinedListRule, spacing: joinedListRule } };
69
+
13
70
  // src/gates/token-style.ts
14
71
  var INLINE_STYLE_TOKEN_KEYS = /^(color|background|backgroundColor|backgroundImage|fill|stroke|fontSize|border|borderTop|borderRight|borderBottom|borderLeft|borderBlock|borderInline|borderStyle|boxShadow|textShadow)$|Color$/;
15
72
  var INLINE_STYLE_MESSAGE = "An inline style that sets a colour, a font size, a border or a shadow is outside the token contract: use a token class. If the value is computed (a palette, a size from a prop), say why in an eslint-disable comment. Width, height and transforms are fine.";
@@ -90,15 +147,28 @@ var WORDS = {
90
147
  merge: "Merge classes with this package's cn() (src/cn.ts), the one place tailwind-merge is taught the type ramp. A plain tailwind-merge drops text-body-2 beside a text colour."
91
148
  }
92
149
  };
150
+ var V = "^(?:(?:[^:\\[\\]\\s]|\\[[^\\]]*\\])+:)*!?";
93
151
  function tokenLint({ tailwindConfig = "tailwind.config.js", audience = "app" } = {}) {
94
152
  const words = WORDS[audience];
153
+ const restrict = [
154
+ { pattern: `${V}text-(?:xs|sm|base|lg|xl|[2-9]xl)$`, message: words.ramp },
155
+ {
156
+ pattern: `${V}(?:text|bg|border|ring|outline|fill|stroke|decoration|divide|placeholder|from|via|to|accent|caret|shadow)-(?:black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)(?:-[0-9]{2,3})?(?:/[0-9]{1,3})?$`,
157
+ message: words.palette
158
+ },
159
+ { pattern: `${V}[a-z-]+-\\[[^\\]]*(?:#[0-9a-fA-F]{3}|rgba?\\(|hsla?\\(|oklch\\()`, message: words.raw },
160
+ {
161
+ pattern: `${V}(?:text|bg|border|ring|outline|fill|stroke|decoration|divide|placeholder|from|via|to|accent|caret|shadow)-(?:bg|text|border|accent|info|warning|success|error)-[a-z-]+/[0-9]{1,3}$`,
162
+ message: words.opacity
163
+ }
164
+ ];
95
165
  return {
96
166
  files: ["**/*.{ts,tsx}"],
97
167
  languageOptions: {
98
168
  parser: typescriptParser,
99
169
  parserOptions: { ecmaFeatures: { jsx: true } }
100
170
  },
101
- plugins: { "better-tailwindcss": betterTailwindcss },
171
+ plugins: { "better-tailwindcss": betterTailwindcss, "token-joined": tokenJoinedPlugin },
102
172
  settings: {
103
173
  "better-tailwindcss": {
104
174
  tailwindConfig,
@@ -107,30 +177,15 @@ function tokenLint({ tailwindConfig = "tailwind.config.js", audience = "app" } =
107
177
  },
108
178
  rules: {
109
179
  "better-tailwindcss/no-unknown-classes": "error",
110
- "better-tailwindcss/no-restricted-classes": [
111
- "error",
112
- {
113
- restrict: [
114
- { pattern: "^(?:[a-z0-9-]+:)*text-(?:xs|sm|base|lg|xl|[2-9]xl)$", message: words.ramp },
115
- {
116
- pattern: "^(?:[a-z0-9-]+:)*(?:text|bg|border|ring|outline|fill|stroke|decoration|divide|placeholder|from|via|to|accent|caret|shadow)-(?:black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)(?:-[0-9]{2,3})?(?:/[0-9]{1,3})?$",
117
- message: words.palette
118
- },
119
- { pattern: "^(?:[a-z0-9-]+:)*[a-z-]+-\\[[^\\]]*(?:#[0-9a-fA-F]{3}|rgba?\\(|hsla?\\(|oklch\\()", message: words.raw },
120
- {
121
- pattern: "^(?:[a-z0-9-]+:)*(?:text|bg|border|ring|outline|fill|stroke|decoration|divide|placeholder|from|via|to|accent|caret|shadow)-(?:bg|text|border|accent|info|warning|success|error)-[a-z-]+/[0-9]{1,3}$",
122
- message: words.opacity
123
- }
124
- ]
125
- }
126
- ],
180
+ "better-tailwindcss/no-restricted-classes": ["error", { restrict }],
181
+ // A class list joined by hand, which the plugin above cannot read (UIG-42).
182
+ "token-joined/classes": ["error", { restrict }],
127
183
  // UIG-21: "use the package's cn(), never a plain twMerge" was a sentence
128
184
  // in all three repos' CLAUDE.md, and nothing checked it.
129
185
  "no-restricted-imports": ["error", { paths: [{ name: "tailwind-merge", message: words.merge }] }]
130
186
  }
131
187
  };
132
188
  }
133
- var V = "^(?:(?:[^:\\[\\]\\s]|\\[[^\\]]*\\])+:)*!?";
134
189
  var NOT_COLOUR = "(?!color:|#|rgba?\\(|hsla?\\(|oklch\\(|var\\()";
135
190
  var HAND_WRITTEN = {
136
191
  type: `${V}(?:text-\\[${NOT_COLOUR}|text-\\[length:|leading-\\[|tracking-\\[)[^\\]]+\\](?:/\\S*)?$`,
@@ -155,55 +210,50 @@ function tokenValues({ audience = "app" } = {}) {
155
210
  }
156
211
  const cornersBySize = {};
157
212
  for (const [name, size] of Object.entries(preset.theme.extend.borderRadius)) (cornersBySize[size] ??= []).push(name === "DEFAULT" ? "rounded" : `rounded-${name}`);
213
+ const values = [
214
+ ...Object.entries(tokensBySize).map(([size, names]) => ({
215
+ pattern: `${V}text-\\[${size.replace(".", "\\.")}\\](?:/\\S*)?$`,
216
+ message: `$0 is the type ramp written by hand. Use ${names.join(" or ")}.`
217
+ })),
218
+ {
219
+ pattern: HAND_WRITTEN.type,
220
+ message: words.type
221
+ },
222
+ ...Object.entries(cornersBySize).map(([size, names]) => ({
223
+ pattern: `${V}rounded(?:-(?:t|r|b|l|s|e|tl|tr|br|bl|ss|se|es|ee))?-\\[${size.replace(".", "\\.")}\\]$`,
224
+ message: `$0 is a corner written by hand. Use ${names.join(" or ")} (with the same side, if it has one).`
225
+ })),
226
+ {
227
+ pattern: HAND_WRITTEN.corner,
228
+ message: words.corner
229
+ },
230
+ {
231
+ pattern: HAND_WRITTEN.shadow,
232
+ message: words.shadow
233
+ }
234
+ ];
235
+ const spacing = [
236
+ {
237
+ pattern: HAND_WRITTEN.spacing,
238
+ message: "$0 is a size or a space written by hand. Use a step of the spacing scale (p-3, h-9, gap-2...) if one fits. Reported, not blocked."
239
+ },
240
+ {
241
+ pattern: HAND_WRITTEN.width,
242
+ message: "$0 is a border or ring width written by hand. Use border, border-2, ring-1... if one fits. Reported, not blocked."
243
+ }
244
+ ];
158
245
  return {
159
246
  files: ["**/*.{ts,tsx}"],
160
247
  ignores: ["**/*.test.ts", "**/*.test.tsx"],
161
- plugins: { "token-values": betterTailwindcss, "token-spacing": betterTailwindcss, "token-style": tokenStylePlugin },
248
+ plugins: { "token-values": betterTailwindcss, "token-spacing": betterTailwindcss, "token-style": tokenStylePlugin, "token-joined": tokenJoinedPlugin },
162
249
  rules: {
163
250
  // A style kept in a const and passed by name (R9): the selector below cannot follow it.
164
251
  "token-style/no-token-style": "error",
165
- "token-values/no-restricted-classes": [
166
- "error",
167
- {
168
- restrict: [
169
- ...Object.entries(tokensBySize).map(([size, names]) => ({
170
- pattern: `${V}text-\\[${size.replace(".", "\\.")}\\](?:/\\S*)?$`,
171
- message: `$0 is the type ramp written by hand. Use ${names.join(" or ")}.`
172
- })),
173
- {
174
- pattern: HAND_WRITTEN.type,
175
- message: words.type
176
- },
177
- ...Object.entries(cornersBySize).map(([size, names]) => ({
178
- pattern: `${V}rounded(?:-(?:t|r|b|l|s|e|tl|tr|br|bl|ss|se|es|ee))?-\\[${size.replace(".", "\\.")}\\]$`,
179
- message: `$0 is a corner written by hand. Use ${names.join(" or ")} (with the same side, if it has one).`
180
- })),
181
- {
182
- pattern: HAND_WRITTEN.corner,
183
- message: words.corner
184
- },
185
- {
186
- pattern: HAND_WRITTEN.shadow,
187
- message: words.shadow
188
- }
189
- ]
190
- }
191
- ],
192
- "token-spacing/no-restricted-classes": [
193
- "warn",
194
- {
195
- restrict: [
196
- {
197
- pattern: HAND_WRITTEN.spacing,
198
- message: "$0 is a size or a space written by hand. Use a step of the spacing scale (p-3, h-9, gap-2...) if one fits. Reported, not blocked."
199
- },
200
- {
201
- pattern: HAND_WRITTEN.width,
202
- message: "$0 is a border or ring width written by hand. Use border, border-2, ring-1... if one fits. Reported, not blocked."
203
- }
204
- ]
205
- }
206
- ],
252
+ "token-values/no-restricted-classes": ["error", { restrict: values }],
253
+ "token-spacing/no-restricted-classes": ["warn", { restrict: spacing }],
254
+ // The same two inside a class list joined by hand (UIG-42).
255
+ "token-joined/values": ["error", { restrict: values }],
256
+ "token-joined/spacing": ["warn", { restrict: spacing }],
207
257
  "no-restricted-syntax": [
208
258
  "error",
209
259
  {
@@ -1107,6 +1157,7 @@ async function runStatus({ root = process.cwd(), app = ".", json = false, detail
1107
1157
  }
1108
1158
 
1109
1159
  export {
1160
+ tokenJoinedPlugin,
1110
1161
  tokenStylePlugin,
1111
1162
  TOKEN_LINT_IGNORES,
1112
1163
  KNOWN_PLUGINS,
@@ -1128,4 +1179,4 @@ export {
1128
1179
  helpers,
1129
1180
  runStatus
1130
1181
  };
1131
- //# sourceMappingURL=chunk-SYVKGOWM.js.map
1182
+ //# sourceMappingURL=chunk-P5W6O5TF.js.map