@ekanos/sdk 0.6.0 → 0.7.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/README.md +95 -13
- package/api-report.md +51 -6
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +2 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/widgets/widget-context.d.ts +17 -0
- package/dist/components/widgets/widget-context.js.map +1 -1
- package/dist/components/widgets/widget-preview-provider.d.ts +4 -2
- package/dist/components/widgets/widget-preview-provider.js +3 -1
- package/dist/components/widgets/widget-preview-provider.js.map +1 -1
- package/dist/components/widgets/widget-state-components.d.ts +59 -3
- package/dist/components/widgets/widget-state-components.js +65 -7
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +124 -21
- package/dist/components/widgets/widget.js +159 -37
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/use-oauth-connection-status.d.ts +17 -1
- package/dist/hooks/use-oauth-connection-status.js +12 -17
- package/dist/hooks/use-oauth-connection-status.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/actor-role.d.ts +14 -0
- package/dist/integration/actor-role.js +21 -0
- package/dist/integration/actor-role.js.map +1 -0
- package/dist/integration/index.d.ts +3 -1
- package/dist/integration/index.js +4 -0
- package/dist/integration/index.js.map +1 -1
- package/dist/integration/types.d.ts +1 -1
- package/dist/integration/types.js.map +1 -1
- package/dist/integration/untrusted-text.d.ts +24 -0
- package/dist/integration/untrusted-text.js +36 -0
- package/dist/integration/untrusted-text.js.map +1 -0
- package/dist/layout/index.d.ts +8 -0
- package/dist/layout/index.js +9 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/widget-layout.d.ts +84 -0
- package/dist/layout/widget-layout.js +138 -0
- package/dist/layout/widget-layout.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock-context.js +3 -1
- package/dist/testing/mock-context.js.map +1 -1
- package/dist/testing/verify-signature.d.ts +26 -0
- package/dist/testing/verify-signature.js +45 -0
- package/dist/testing/verify-signature.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/integration.d.ts +5 -0
- package/dist/types/integration.js.map +1 -1
- package/dist/types/widget-verdict.d.ts +6 -0
- package/dist/types/widget-verdict.js +2 -0
- package/dist/types/widget-verdict.js.map +1 -0
- package/eslint.design-preset.mjs +226 -0
- package/eslint.design-rules.mjs +437 -0
- package/package.json +15 -5
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The design-system rules the shadcn linter does not cover — eight of them,
|
|
3
|
+
* as one lint plugin object. The rules and their reasoning live in the design system's
|
|
4
|
+
* rules; each message names the rule.
|
|
5
|
+
*
|
|
6
|
+
* ── Why this file sits in a published package ────────────────────────────
|
|
7
|
+
* Two consumers need the same eight rules and neither may fork them:
|
|
8
|
+
*
|
|
9
|
+
* - the host repo's own lint, as an oxlint JS plugin. `.oxlintrc.json`
|
|
10
|
+
* still loads this plugin through a
|
|
11
|
+
* two-line re-export of this file, so the plugin name, the rule names
|
|
12
|
+
* and their severities are untouched.
|
|
13
|
+
* - a partner's own lint, as an ESLint plugin, through
|
|
14
|
+
* `@ekanos/sdk/eslint/design` (`eslint.design-preset.mjs`, its sibling
|
|
15
|
+
* here), which a scaffolded project wires up by default.
|
|
16
|
+
*
|
|
17
|
+
* The host's tooling is not published, so the import can only run this
|
|
18
|
+
* direction: the rules live in the package a partner installs and the host
|
|
19
|
+
* repo's tooling reaches in. The host's capability lint already reads its
|
|
20
|
+
* preset out of `eslint.preset.mjs` the same way, only for the plugin rather
|
|
21
|
+
* than the config.
|
|
22
|
+
*
|
|
23
|
+
* ── What it may depend on ────────────────────────────────────────────────
|
|
24
|
+
* Nothing. Every rule below is plain JS over the ESTree/JSX AST with
|
|
25
|
+
* `context.report`, no oxlint-only API and no import of any kind, which is
|
|
26
|
+
* what lets one implementation satisfy both linters. Keep it that way: a
|
|
27
|
+
* dependency here would either break oxlint's plugin loader or grow the
|
|
28
|
+
* SDK's install for a lint plugin.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The AST these rules read, typed in JSDoc because `eslint.design-preset.mjs`
|
|
33
|
+
* imports this file and that pulls it into the package's TypeScript program.
|
|
34
|
+
* Deliberately loose: a lint rule is handed whatever the parser produced and
|
|
35
|
+
* narrows on `type` itself, so one node type stands in for several families
|
|
36
|
+
* and every field is optional.
|
|
37
|
+
*
|
|
38
|
+
* @typedef {{ type?: string, name?: string }} AstName
|
|
39
|
+
*
|
|
40
|
+
* @typedef {object} Expression
|
|
41
|
+
* @property {string} [type]
|
|
42
|
+
* @property {string | number | boolean | null} [value]
|
|
43
|
+
* @property {Expression} [expression]
|
|
44
|
+
* @property {Expression[]} [arguments]
|
|
45
|
+
* @property {TemplateChunk[]} [quasis]
|
|
46
|
+
* @property {Expression} [left]
|
|
47
|
+
* @property {Expression} [right]
|
|
48
|
+
* @property {Expression} [consequent]
|
|
49
|
+
* @property {Expression} [alternate]
|
|
50
|
+
*
|
|
51
|
+
* @typedef {{ value: { cooked?: string | null } }} TemplateChunk
|
|
52
|
+
* @typedef {{ value?: string | number | boolean | null }} StringLiteral
|
|
53
|
+
*
|
|
54
|
+
* @typedef {object} JsxAttribute
|
|
55
|
+
* @property {string} [type]
|
|
56
|
+
* @property {AstName} [name]
|
|
57
|
+
* @property {Expression | null} [value]
|
|
58
|
+
*
|
|
59
|
+
* @typedef {object} JsxElement
|
|
60
|
+
* @property {AstName} [name]
|
|
61
|
+
* @property {JsxAttribute[]} [attributes]
|
|
62
|
+
* @property {JsxElement} [openingElement]
|
|
63
|
+
* @property {JsxElement} [parent]
|
|
64
|
+
* @property {string} [type]
|
|
65
|
+
*
|
|
66
|
+
* @typedef {{ report: (descriptor: { node: unknown, message: string }) => void }} RuleContext
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
const FADED_MUTED = /(?:^|[\s:'"`])(?:[\w-]+:)*text-muted-foreground\/\d+/;
|
|
70
|
+
|
|
71
|
+
/** `bg-white`, `text-black/50`, `dark:border-white`… — @shadcn/lint's
|
|
72
|
+
* no-raw-colors covers the palette scales but not these two. */
|
|
73
|
+
const ABSOLUTE_COLOR =
|
|
74
|
+
/(?:^|[\s'"`])(?:[\w-]+:)*(?:bg|text|border|ring|outline|divide|fill|stroke|from|via|to|decoration|placeholder|caret|accent|shadow)-(?:white|black)(?:\/\d+)?(?=$|[\s'"`])/;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Visits every string and template chunk, so class names in `cn()`, cva
|
|
78
|
+
* maps and plain constants are all checked, not only `className=`.
|
|
79
|
+
*
|
|
80
|
+
* @param {RegExp} test
|
|
81
|
+
* @param {string} message
|
|
82
|
+
*/
|
|
83
|
+
function stringVisitors(test, message) {
|
|
84
|
+
/** @param {RuleContext} context */
|
|
85
|
+
return (context) => {
|
|
86
|
+
/** @param {unknown} node @param {unknown} value */
|
|
87
|
+
const check = (node, value) => {
|
|
88
|
+
if (typeof value === 'string' && test.test(value)) {
|
|
89
|
+
context.report({ node, message });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
/** @param {StringLiteral} node */
|
|
95
|
+
Literal: (node) => check(node, node.value),
|
|
96
|
+
/** @param {TemplateChunk} node */
|
|
97
|
+
TemplateElement: (node) => check(node, node.value.cooked),
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const BADGE_TONES = new Set(['success', 'warning', 'info']);
|
|
103
|
+
|
|
104
|
+
const TONE_BADGE_MESSAGE =
|
|
105
|
+
'A tone is not a Badge variant. A record\u2019s state is <StatusBadge tone="…"> from @kit/ui/status-badge, whose dot carries the tone so meaning never rides on hue alone; Badge is for counts, categories and short labels (design-system status-badge rule).';
|
|
106
|
+
|
|
107
|
+
/** @param {Expression | null | undefined} value */
|
|
108
|
+
function literalValue(value) {
|
|
109
|
+
if (!value) return null;
|
|
110
|
+
if (value.type === 'Literal') return value.value;
|
|
111
|
+
if (
|
|
112
|
+
value.type === 'JSXExpressionContainer' &&
|
|
113
|
+
value.expression?.type === 'Literal'
|
|
114
|
+
) {
|
|
115
|
+
return value.expression.value;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {AstName | undefined} name */
|
|
121
|
+
function jsxName(name) {
|
|
122
|
+
return name?.type === 'JSXIdentifier' ? name.name : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const OVERLAY_CONTENT = new Set([
|
|
126
|
+
'DialogContent',
|
|
127
|
+
'SheetContent',
|
|
128
|
+
'AlertDialogContent',
|
|
129
|
+
'DrawerContent',
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `max-w-md`, `sm:w-[560px]`… but not the mobile-width `w-full` / `w-auto`.
|
|
134
|
+
*
|
|
135
|
+
* A variant prefix may itself contain brackets (`data-[swipe-direction=right]:`),
|
|
136
|
+
* so the prefix group has to allow them — `[\w-]+:` alone stopped at the `[`
|
|
137
|
+
* and let `data-[swipe-direction=right]:sm:max-w-xl` through on two drawer
|
|
138
|
+
* shells for as long as the rule has existed.
|
|
139
|
+
*/
|
|
140
|
+
const OVERLAY_WIDTH =
|
|
141
|
+
/(?:^|\s)(?:(?:[\w-]+|\[[^\]]*\]|[\w-]+\[[^\]]*\])+:)*(?:max-w|min-w|w)-(?!full\b|auto\b)\S+/;
|
|
142
|
+
|
|
143
|
+
const OVERLAY_WIDTH_MESSAGE =
|
|
144
|
+
'Never set an overlay’s width in className. Dialog, Sheet and Drawer take size="sm|md|lg|xl|2xl|3xl|full" from @kit/ui/overlay-size; AlertDialog takes size="sm" (design-system overlay rule).';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Every string literal in a className value, including inside `cn(...)`.
|
|
148
|
+
*
|
|
149
|
+
* @param {Expression | null | undefined} value
|
|
150
|
+
* @returns {StringLiteral[]}
|
|
151
|
+
*/
|
|
152
|
+
function classNameStrings(value) {
|
|
153
|
+
if (!value) return [];
|
|
154
|
+
if (value.type === 'Literal') return [value];
|
|
155
|
+
if (value.type === 'JSXExpressionContainer') {
|
|
156
|
+
return classNameStrings(value.expression);
|
|
157
|
+
}
|
|
158
|
+
if (value.type === 'CallExpression') {
|
|
159
|
+
return (value.arguments ?? []).flatMap(classNameStrings);
|
|
160
|
+
}
|
|
161
|
+
if (value.type === 'TemplateLiteral') {
|
|
162
|
+
return (value.quasis ?? []).map((quasi) => ({
|
|
163
|
+
...quasi,
|
|
164
|
+
value: quasi.value.cooked,
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
if (value.type === 'LogicalExpression' || value.type === 'BinaryExpression') {
|
|
168
|
+
return [...classNameStrings(value.left), ...classNameStrings(value.right)];
|
|
169
|
+
}
|
|
170
|
+
if (value.type === 'ConditionalExpression') {
|
|
171
|
+
return [
|
|
172
|
+
...classNameStrings(value.consequent),
|
|
173
|
+
...classNameStrings(value.alternate),
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A rounded hover band with side padding: a row drawn as a pill. Square
|
|
180
|
+
* icon buttons (`size-8`, `h-6 w-6`) are a different shape and pass, and so
|
|
181
|
+
* does a bordered or filled tile, which is a card rather than a row. */
|
|
182
|
+
/** @param {string} value */
|
|
183
|
+
function isRoundedRowHover(value) {
|
|
184
|
+
if (/(?:^|\s)border(?=$|\s)/.test(value) || /(?:^|\s)bg-/.test(value)) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (
|
|
189
|
+
!/\brounded-(?:md|lg|xl)\b/.test(value) ||
|
|
190
|
+
!/(?:^|\s)hover:bg-/.test(value) ||
|
|
191
|
+
!/(?:^|\s)(?:[\w-]+:)*px-/.test(value) ||
|
|
192
|
+
!/(?:^|\s)(?:w-full|justify-between)(?=$|\s)/.test(value)
|
|
193
|
+
) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (/(?:^|\s)(?:[\w-]+:)*size-/.test(value)) return false;
|
|
198
|
+
|
|
199
|
+
const height = /(?:^|\s)h-([\w.[\]]+)(?=$|\s)/.exec(value);
|
|
200
|
+
const width = /(?:^|\s)w-([\w.[\]]+)(?=$|\s)/.exec(value);
|
|
201
|
+
|
|
202
|
+
return !(height && width && height[1] === width[1]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const CHIP_TONES = ['success', 'primary', 'warning', 'destructive', 'info'];
|
|
206
|
+
|
|
207
|
+
const CHIP_SMALL_TEXT = /(?:^|\s)(?:[\w-]+:)*text-(?:xs|sm)(?=$|\s)/;
|
|
208
|
+
|
|
209
|
+
/** Base-tone small text on a tint of the same tone. */
|
|
210
|
+
/** @param {string} value */
|
|
211
|
+
function isWeakTintedChip(value) {
|
|
212
|
+
if (!CHIP_SMALL_TEXT.test(value)) return false;
|
|
213
|
+
|
|
214
|
+
return CHIP_TONES.some(
|
|
215
|
+
(tone) =>
|
|
216
|
+
new RegExp(`(?:^|\\s)(?:[\\w-]+:)*bg-${tone}/\\d+`).test(value) &&
|
|
217
|
+
new RegExp(`(?:^|\\s)(?:[\\w-]+:)*text-${tone}(?![\\w-])`).test(value),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {(value: string) => boolean} predicate
|
|
223
|
+
* @param {string} message
|
|
224
|
+
*/
|
|
225
|
+
function predicateVisitors(predicate, message) {
|
|
226
|
+
/** @param {RuleContext} context */
|
|
227
|
+
return (context) => {
|
|
228
|
+
/** @param {unknown} node @param {unknown} value */
|
|
229
|
+
const check = (node, value) => {
|
|
230
|
+
if (typeof value === 'string' && predicate(value)) {
|
|
231
|
+
context.report({ node, message });
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
/** @param {StringLiteral} node */
|
|
237
|
+
Literal: (node) => check(node, node.value),
|
|
238
|
+
/** @param {TemplateChunk} node */
|
|
239
|
+
TemplateElement: (node) => check(node, node.value.cooked),
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* A viewport breakpoint variant anywhere in a class token's variant chain:
|
|
246
|
+
* `sm:grid-cols-2`, `hover:md:flex`, `max-lg:hidden`. Container variants
|
|
247
|
+
* (`@sm/widget:`) start with `@` and pass.
|
|
248
|
+
*/
|
|
249
|
+
const VIEWPORT_BREAKPOINT =
|
|
250
|
+
/(?:^|\s)(?:[\w-]+:)*(?:max-)?(?:sm|md|lg|xl|2xl):[^\s:]/;
|
|
251
|
+
|
|
252
|
+
const TOOLBAR_TAB_ELEMENTS = new Set(['TabsList', 'ScopePills', 'ScopeLinks']);
|
|
253
|
+
const TOOLBARS = new Set(['PageToolbar', 'PageToolbarActions']);
|
|
254
|
+
|
|
255
|
+
export default {
|
|
256
|
+
meta: { name: 'design' },
|
|
257
|
+
rules: {
|
|
258
|
+
'no-tone-badge-variant': {
|
|
259
|
+
meta: {
|
|
260
|
+
type: 'problem',
|
|
261
|
+
docs: {
|
|
262
|
+
description:
|
|
263
|
+
'Disallow a status tone as a Badge variant; a record state is StatusBadge.',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
/** @param {RuleContext} context */
|
|
267
|
+
create(context) {
|
|
268
|
+
return {
|
|
269
|
+
/** @param {JsxElement} node */
|
|
270
|
+
JSXOpeningElement(node) {
|
|
271
|
+
if (
|
|
272
|
+
node.name?.type !== 'JSXIdentifier' ||
|
|
273
|
+
node.name.name !== 'Badge'
|
|
274
|
+
) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (const attribute of node.attributes ?? []) {
|
|
279
|
+
if (
|
|
280
|
+
attribute.type !== 'JSXAttribute' ||
|
|
281
|
+
attribute.name?.name !== 'variant'
|
|
282
|
+
) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const value = literalValue(attribute.value);
|
|
287
|
+
|
|
288
|
+
if (typeof value === 'string' && BADGE_TONES.has(value)) {
|
|
289
|
+
context.report({
|
|
290
|
+
node: attribute,
|
|
291
|
+
message: TONE_BADGE_MESSAGE,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
'no-faded-muted-text': {
|
|
300
|
+
meta: {
|
|
301
|
+
type: 'problem',
|
|
302
|
+
docs: {
|
|
303
|
+
description:
|
|
304
|
+
'Disallow an opacity modifier on text-muted-foreground; every step below 100 fails contrast somewhere.',
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
create: stringVisitors(
|
|
308
|
+
FADED_MUTED,
|
|
309
|
+
'Opacity on text-muted-foreground fails WCAG AA: even /90 measures 4.18:1 on a dark card. Use plain text-muted-foreground, a smaller size if it must recede, or text-border for a decorative separator (design-system opacity-on-muted rule).',
|
|
310
|
+
),
|
|
311
|
+
},
|
|
312
|
+
'no-absolute-colors': {
|
|
313
|
+
meta: {
|
|
314
|
+
type: 'problem',
|
|
315
|
+
docs: {
|
|
316
|
+
description:
|
|
317
|
+
'Disallow white/black color utilities; they ignore the theme, dark mode and Source branding.',
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
create: stringVisitors(
|
|
321
|
+
ABSOLUTE_COLOR,
|
|
322
|
+
'white/black color utilities ignore the theme, dark mode and Source branding. Use a token: bg-card or bg-background for surfaces, text-foreground / text-primary-foreground for text. A modal scrim may stay black with a disable comment saying so (design-system theming rule).',
|
|
323
|
+
),
|
|
324
|
+
},
|
|
325
|
+
'no-overlay-width-class': {
|
|
326
|
+
meta: {
|
|
327
|
+
type: 'problem',
|
|
328
|
+
docs: {
|
|
329
|
+
description:
|
|
330
|
+
'Disallow width classes on overlay content; overlays take a size from the overlay-size scale.',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
/** @param {RuleContext} context */
|
|
334
|
+
create(context) {
|
|
335
|
+
return {
|
|
336
|
+
/** @param {JsxElement} node */
|
|
337
|
+
JSXOpeningElement(node) {
|
|
338
|
+
if (!OVERLAY_CONTENT.has(jsxName(node.name) ?? '')) return;
|
|
339
|
+
|
|
340
|
+
for (const attribute of node.attributes ?? []) {
|
|
341
|
+
if (
|
|
342
|
+
attribute.type !== 'JSXAttribute' ||
|
|
343
|
+
attribute.name?.name !== 'className'
|
|
344
|
+
) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const flagged = classNameStrings(attribute.value).some(
|
|
349
|
+
(literal) =>
|
|
350
|
+
typeof literal.value === 'string' &&
|
|
351
|
+
OVERLAY_WIDTH.test(literal.value),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
if (flagged) {
|
|
355
|
+
context.report({
|
|
356
|
+
node: attribute,
|
|
357
|
+
message: OVERLAY_WIDTH_MESSAGE,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
'no-rounded-row-hover': {
|
|
366
|
+
meta: {
|
|
367
|
+
type: 'problem',
|
|
368
|
+
docs: {
|
|
369
|
+
description:
|
|
370
|
+
'Disallow a rounded hover pill on a padded row; rows are flat edge-to-edge bands.',
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
create: predicateVisitors(
|
|
374
|
+
isRoundedRowHover,
|
|
375
|
+
'Rows are flat edge-to-edge bands: padding on the row (px-5 py-3), a flat hover:bg-accent/50, never a rounded pill with side padding (design-system edge-to-edge rows rule).',
|
|
376
|
+
),
|
|
377
|
+
},
|
|
378
|
+
'tinted-chip-strong': {
|
|
379
|
+
meta: {
|
|
380
|
+
type: 'problem',
|
|
381
|
+
docs: {
|
|
382
|
+
description:
|
|
383
|
+
'Require the -strong text step for small text on a tint of its own colour.',
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
create: predicateVisitors(
|
|
387
|
+
isWeakTintedChip,
|
|
388
|
+
'Small text on a tint of its own colour takes the -strong step: text-success-strong on bg-success/10, text-primary-strong on bg-primary/10. The base token fails AA once it sits on its own wash; it stays right for the fill and for icons (design-system tinted-chip rule).',
|
|
389
|
+
),
|
|
390
|
+
},
|
|
391
|
+
'no-viewport-breakpoints-in-widgets': {
|
|
392
|
+
meta: {
|
|
393
|
+
type: 'problem',
|
|
394
|
+
docs: {
|
|
395
|
+
description:
|
|
396
|
+
'Disallow viewport breakpoints in widget code; a widget sizes to its own container.',
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
create: stringVisitors(
|
|
400
|
+
VIEWPORT_BREAKPOINT,
|
|
401
|
+
'A widget sits in a half-width column, a full row or a tile, whatever the viewport, so sm:/md:/lg: misfire. Size to the card with container queries: Widget.Card is @container/widget, so write @sm/widget:grid-cols-2 (design-system widget rules).',
|
|
402
|
+
),
|
|
403
|
+
},
|
|
404
|
+
'no-toolbar-tabs': {
|
|
405
|
+
meta: {
|
|
406
|
+
type: 'problem',
|
|
407
|
+
docs: {
|
|
408
|
+
description: 'Disallow tabs or scope pills inside a PageToolbar.',
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
/** @param {RuleContext} context */
|
|
412
|
+
create(context) {
|
|
413
|
+
return {
|
|
414
|
+
/** @param {JsxElement} node */
|
|
415
|
+
JSXOpeningElement(node) {
|
|
416
|
+
if (!TOOLBAR_TAB_ELEMENTS.has(jsxName(node.name) ?? '')) return;
|
|
417
|
+
|
|
418
|
+
for (let parent = node.parent; parent; parent = parent.parent) {
|
|
419
|
+
if (
|
|
420
|
+
parent.type === 'JSXElement' &&
|
|
421
|
+
parent !== node.parent &&
|
|
422
|
+
TOOLBARS.has(jsxName(parent.openingElement?.name) ?? '')
|
|
423
|
+
) {
|
|
424
|
+
context.report({
|
|
425
|
+
node,
|
|
426
|
+
message:
|
|
427
|
+
'No tabs inside a PageToolbar. View tabs go in PageTitleSection’s `tabs` prop; a filter over one list goes in that card’s ListMasthead (page-layout view-tabs rule).',
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The official SDK for building Ekanos integrations.",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"email": "npm@govastly.com"
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"README.md",
|
|
13
13
|
"api-report.md",
|
|
14
14
|
"LICENSE",
|
|
15
|
-
"eslint.preset.mjs"
|
|
15
|
+
"eslint.preset.mjs",
|
|
16
|
+
"eslint.design-preset.mjs",
|
|
17
|
+
"eslint.design-rules.mjs"
|
|
16
18
|
],
|
|
17
19
|
"type": "module",
|
|
18
20
|
"sideEffects": [
|
|
@@ -23,6 +25,9 @@
|
|
|
23
25
|
"context": [
|
|
24
26
|
"dist/context/index.d.ts"
|
|
25
27
|
],
|
|
28
|
+
"layout": [
|
|
29
|
+
"dist/layout/index.d.ts"
|
|
30
|
+
],
|
|
26
31
|
"testing": [
|
|
27
32
|
"dist/testing/index.d.ts"
|
|
28
33
|
],
|
|
@@ -51,6 +56,10 @@
|
|
|
51
56
|
"types": "./dist/context/index.d.ts",
|
|
52
57
|
"default": "./dist/context/index.js"
|
|
53
58
|
},
|
|
59
|
+
"./layout": {
|
|
60
|
+
"types": "./dist/layout/index.d.ts",
|
|
61
|
+
"default": "./dist/layout/index.js"
|
|
62
|
+
},
|
|
54
63
|
"./testing": {
|
|
55
64
|
"types": "./dist/testing/index.d.ts",
|
|
56
65
|
"default": "./dist/testing/index.js"
|
|
@@ -59,14 +68,15 @@
|
|
|
59
68
|
"types": "./dist/integration/index.d.ts",
|
|
60
69
|
"default": "./dist/integration/index.js"
|
|
61
70
|
},
|
|
62
|
-
"./eslint": "./eslint.preset.mjs"
|
|
71
|
+
"./eslint": "./eslint.preset.mjs",
|
|
72
|
+
"./eslint/design": "./eslint.design-preset.mjs"
|
|
63
73
|
},
|
|
64
74
|
"publishConfig": {
|
|
65
75
|
"access": "public"
|
|
66
76
|
},
|
|
67
77
|
"dependencies": {
|
|
68
|
-
"@ekanos/integration-schema": "0.
|
|
69
|
-
"@ekanos/ui": "0.
|
|
78
|
+
"@ekanos/integration-schema": "0.7.0",
|
|
79
|
+
"@ekanos/ui": "0.7.0",
|
|
70
80
|
"@supabase/supabase-js": "2.87.1",
|
|
71
81
|
"server-only": "^0.0.1"
|
|
72
82
|
},
|