@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.
- package/dist/eslint/index.js +21 -0
- package/dist/eslint/index.js.map +2 -2
- package/dist/eslint/no-handmade-empty-state.d.ts +7 -0
- package/dist/eslint/no-handmade-empty-state.d.ts.map +1 -1
- package/dist/gates/app-checks.d.ts.map +1 -1
- package/dist/gates/{chunk-SYVKGOWM.js → chunk-P5W6O5TF.js} +114 -63
- package/dist/gates/chunk-P5W6O5TF.js.map +7 -0
- package/dist/gates/cli.js +1 -1
- package/dist/gates/gate-config.d.ts.map +1 -1
- package/dist/gates/index.js +13 -3
- package/dist/gates/index.js.map +2 -2
- package/dist/gates/token-joined.d.ts +28 -0
- package/dist/gates/token-joined.d.ts.map +1 -0
- package/dist/gates/token-lint.d.ts.map +1 -1
- package/package.json +1 -1
- package/registry.json +1 -1
- package/src/EmptyState.mdx +1 -1
- package/src/eslint/no-handmade-empty-state.test.ts +23 -1
- package/src/eslint/no-handmade-empty-state.ts +37 -1
- package/src/gates/app-checks.ts +7 -0
- package/src/gates/gate-config.ts +2 -0
- package/src/gates/gates.test.ts +26 -0
- package/src/gates/token-joined.ts +94 -0
- package/src/gates/token-lint.ts +68 -66
- package/dist/gates/chunk-SYVKGOWM.js.map +0 -7
|
@@ -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;
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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-
|
|
1182
|
+
//# sourceMappingURL=chunk-P5W6O5TF.js.map
|