@carto/meridian-ds 5.1.2 → 5.1.4-alpha.0360fb1.341
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/CHANGELOG.md +7 -0
- package/dist/bin/meridian-ds-mcp.js +37 -42
- package/dist/conventions.md +28 -2
- package/dist/eslint-rules/index.cjs +63 -0
- package/dist/eslint-rules/index.js +63 -0
- package/dist/metadata.json +65 -215
- package/dist/spacing-steps-CN4lgz20.js +21 -0
- package/dist/spacing-steps-DOjPPOKL.cjs +20 -0
- package/dist/theme/index.cjs +2 -0
- package/dist/theme/index.js +2 -0
- package/dist/types/custom-icons/__docs__/icon-keywords.d.ts +1 -2
- package/dist/types/custom-icons/__docs__/icon-keywords.d.ts.map +1 -1
- package/dist/types/eslint-rules/index.d.ts +45 -0
- package/dist/types/eslint-rules/index.d.ts.map +1 -0
- package/dist/types/eslint-rules/index.test.d.ts +2 -0
- package/dist/types/eslint-rules/index.test.d.ts.map +1 -0
- package/dist/types/utils/spacing-steps.d.ts +4 -0
- package/dist/types/utils/spacing-steps.d.ts.map +1 -0
- package/dist/types/utils/theme-constants.d.ts +2 -0
- package/dist/types/utils/theme-constants.d.ts.map +1 -1
- package/package.json +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- feat(ai-metadata): compact MCP, design-review conventions and Meridian ESLint design rules [sc-578601] [sc-578639] [sc-579313] [#563](https://github.com/CartoDB/meridian-ds/pull/563)
|
|
6
|
+
|
|
5
7
|
## 5.0
|
|
6
8
|
|
|
9
|
+
### 5.1.3
|
|
10
|
+
|
|
11
|
+
- fix(ai-metadata): allow fractional spacing steps; give design team ownership of design-system paths [sc-577069] [#558](https://github.com/CartoDB/meridian-ds/pull/558)
|
|
12
|
+
- feat(ci): notify Slack when a PR needs design review [#558](https://github.com/CartoDB/meridian-ds/pull/558)
|
|
13
|
+
|
|
7
14
|
### 5.1.2
|
|
8
15
|
|
|
9
16
|
- fix(dialog-header): Name the dialog only by its title [sc-577444] [#556](https://github.com/CartoDB/meridian-ds/pull/556)
|
|
@@ -40,7 +40,7 @@ var VERSION = "0.0.0";
|
|
|
40
40
|
var TOOLS = [
|
|
41
41
|
{
|
|
42
42
|
name: "meridian_list_components",
|
|
43
|
-
description: "
|
|
43
|
+
description: "The Meridian component catalog \u2014 start here. Each row has a description and keywords, plus by name: `subComponents` (parts it's composed with), `variants` (siblings chosen between) and `relatedExports` (hooks/utils/constants). Pick by meaning (there's no search tool), then call meridian_get_component. `documented: false` marks components with no curation yet \u2014 lean on meridian_get_examples and the .d.ts for those.",
|
|
44
44
|
inputSchema: {
|
|
45
45
|
type: "object",
|
|
46
46
|
properties: {},
|
|
@@ -49,19 +49,23 @@ var TOOLS = [
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
name: "meridian_get_component",
|
|
52
|
-
description: "
|
|
52
|
+
description: "Everything to use a component, in one call: when to use it, its decisionTree (when another component fits better), limitations, high-value inherited MUI props (`curation.mui`), family members with descriptions, the lead usage story's code (`examples.stories[0].code`) and the other story ids. Own props/types/JSDoc ship in the .d.ts \u2014 `propTypes` gives the command. Pass `names` for several components at once. A family member's name (e.g. `DialogHeader`) returns a pointer to its parent.",
|
|
53
53
|
inputSchema: {
|
|
54
54
|
type: "object",
|
|
55
55
|
properties: {
|
|
56
|
-
name: { type: "string", description: 'Component name, e.g. "Button"' }
|
|
56
|
+
name: { type: "string", description: 'Component name, e.g. "Button"' },
|
|
57
|
+
names: {
|
|
58
|
+
type: "array",
|
|
59
|
+
items: { type: "string" },
|
|
60
|
+
description: 'Several component names, e.g. ["SplitButton", "Menu"]. Returns one result per name, in order. Use instead of `name`.'
|
|
61
|
+
}
|
|
57
62
|
},
|
|
58
|
-
required: ["name"],
|
|
59
63
|
additionalProperties: false
|
|
60
64
|
}
|
|
61
65
|
},
|
|
62
66
|
{
|
|
63
67
|
name: "meridian_get_examples",
|
|
64
|
-
description: "
|
|
68
|
+
description: "Copy-able example code from a component's stories. With no storyId: the lead usage story and the notable stories in full, plus an index (name + id) of the rest. With a storyId: just that story. Family member names point to their parent, as in meridian_get_component.",
|
|
65
69
|
inputSchema: {
|
|
66
70
|
type: "object",
|
|
67
71
|
properties: {
|
|
@@ -77,7 +81,7 @@ var TOOLS = [
|
|
|
77
81
|
},
|
|
78
82
|
{
|
|
79
83
|
name: "meridian_get_conventions",
|
|
80
|
-
description: "Mandatory system-wide Meridian rules
|
|
84
|
+
description: "Mandatory system-wide Meridian rules (component choice, a11y, i18n, styling/tokens, props, examples). Call once per session BEFORE writing UI code and follow them \u2014 the per-component data doesn't repeat them.",
|
|
81
85
|
inputSchema: {
|
|
82
86
|
type: "object",
|
|
83
87
|
properties: {},
|
|
@@ -86,7 +90,7 @@ var TOOLS = [
|
|
|
86
90
|
},
|
|
87
91
|
{
|
|
88
92
|
name: "meridian_list_icons",
|
|
89
|
-
description: "
|
|
93
|
+
description: "Every Meridian custom icon by name, with search keywords where curated (e.g. `Bigquery: warehouse, database`). Match by meaning; import from '@carto/meridian-ds/custom-icons' and render `<Name />`. Prefer these over an ad-hoc SVG or a MUI icon.",
|
|
90
94
|
inputSchema: {
|
|
91
95
|
type: "object",
|
|
92
96
|
properties: {},
|
|
@@ -95,7 +99,7 @@ var TOOLS = [
|
|
|
95
99
|
},
|
|
96
100
|
{
|
|
97
101
|
name: "meridian_get_tokens",
|
|
98
|
-
description: "
|
|
102
|
+
description: "Exact theme token values to use instead of hardcoding: palette, typography ramp, spacing base, shape.borderRadius, breakpoints and shadows \u2014 projected from the installed version's theme. Theme-level only: a component's internal spacing comes from meridian_get_component.",
|
|
99
103
|
inputSchema: {
|
|
100
104
|
type: "object",
|
|
101
105
|
properties: {},
|
|
@@ -104,7 +108,7 @@ var TOOLS = [
|
|
|
104
108
|
},
|
|
105
109
|
{
|
|
106
110
|
name: "meridian_get_screenshot",
|
|
107
|
-
description: "
|
|
111
|
+
description: "Path to a labeled reference image. name='icons' is a grid of every custom icon captioned with its import name \u2014 Read it to confirm an icon visually. Needs an image-capable runtime. Omit name to list the available screenshots.",
|
|
108
112
|
inputSchema: {
|
|
109
113
|
type: "object",
|
|
110
114
|
properties: {
|
|
@@ -168,7 +172,8 @@ function exampleStories(examples) {
|
|
|
168
172
|
id: e.id,
|
|
169
173
|
name: titleize(e.name),
|
|
170
174
|
...e.id === leadId ? { lead: true } : {},
|
|
171
|
-
...e.summary ? { summary: e.summary } : {}
|
|
175
|
+
...e.summary ? { summary: e.summary } : {},
|
|
176
|
+
...e.id === leadId ? { code: e.code } : {}
|
|
172
177
|
}));
|
|
173
178
|
}
|
|
174
179
|
function listComponents() {
|
|
@@ -180,20 +185,14 @@ function listComponents() {
|
|
|
180
185
|
// examples + the .d.ts are still available. Documented rows omit the flag.
|
|
181
186
|
...d.documented ? {} : { documented: false },
|
|
182
187
|
keywords: d.curation.keywords ?? [],
|
|
183
|
-
// Family
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
...d.curation.
|
|
187
|
-
...d.curation.variants?.length ? { variants: d.curation.variants } : {},
|
|
188
|
-
// The public hooks/utils/constants that ship with this component — name,
|
|
189
|
-
// kind, and description (same as subComponents/variants, so an agent can pick
|
|
190
|
-
// one while scanning without a get_component round-trip). get_component adds
|
|
191
|
-
// `import` (the entry point — constant across the family, so omitted here).
|
|
188
|
+
// Family members and related exports by name only; get_component carries
|
|
189
|
+
// their descriptions.
|
|
190
|
+
...d.curation.subComponents?.length ? { subComponents: d.curation.subComponents.map(subName) } : {},
|
|
191
|
+
...d.curation.variants?.length ? { variants: d.curation.variants.map(subName) } : {},
|
|
192
192
|
...d.curation.relatedExports?.length ? {
|
|
193
193
|
relatedExports: d.curation.relatedExports.map((e) => ({
|
|
194
194
|
name: relatedName(e),
|
|
195
|
-
...typeof e === "object" && e.kind ? { kind: e.kind } : {}
|
|
196
|
-
...typeof e === "object" && e.description ? { description: e.description } : {}
|
|
195
|
+
...typeof e === "object" && e.kind ? { kind: e.kind } : {}
|
|
197
196
|
}))
|
|
198
197
|
} : {}
|
|
199
198
|
}));
|
|
@@ -219,7 +218,7 @@ function getComponent(name) {
|
|
|
219
218
|
// are already in `curation.mui` above.
|
|
220
219
|
propTypes: `${compound}Own props, exact types, and JSDoc are not in this payload \u2014 they ship as TypeScript declarations. Read them from the installed package: \`${dtsGlob}\`. Import from '@carto/meridian-ds/components'.`,
|
|
221
220
|
examples: doc.examples.length ? {
|
|
222
|
-
note: "Stories are ordered by copy-value; the flagged lead is the starting point. Call meridian_get_examples(name
|
|
221
|
+
note: "Stories are ordered by copy-value; the flagged lead (with its code) is the starting point. Call meridian_get_examples(name, storyId) for any other story.",
|
|
223
222
|
stories: exampleStories(doc.examples)
|
|
224
223
|
} : {
|
|
225
224
|
note: "No example stories ship for this component yet \u2014 read its props from the .d.ts (see propTypes above).",
|
|
@@ -295,12 +294,15 @@ function getScreenshot(name) {
|
|
|
295
294
|
return { path: shot.path, count: shot.count, hint: shot.hint };
|
|
296
295
|
}
|
|
297
296
|
function listIcons() {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
icons:
|
|
303
|
-
|
|
297
|
+
const lines = ICONS.map(
|
|
298
|
+
(i) => i.keywords?.length ? `${i.name}: ${i.keywords.join(", ")}` : i.name
|
|
299
|
+
);
|
|
300
|
+
return [
|
|
301
|
+
`${ICONS.length} Meridian custom icons. Import from '@carto/meridian-ds/custom-icons' and render as <Name />. Match by name or keywords \u2014 there's no search tool. To see the glyphs, call meridian_get_screenshot({name:'icons'}) and Read the returned image path.`,
|
|
302
|
+
"Props: every icon is an identical forwardRef component sharing one prop type, CustomIconProps: MUI SvgIconProps \u2014 color, fontSize ('small'|'medium'|'large'|'inherit'), sx, htmlColor, titleAccess \u2014 plus width/height (number|string) to override the fontSize-based size. Prefer color/fontSize for theming/sizing; width/height only for a specific px size. The exact type ships at node_modules/@carto/meridian-ds/dist/types/custom-icons/types.d.ts.",
|
|
303
|
+
"Icons (Name: keywords):",
|
|
304
|
+
...lines
|
|
305
|
+
].join("\n");
|
|
304
306
|
}
|
|
305
307
|
function callTool(name, args) {
|
|
306
308
|
switch (name) {
|
|
@@ -313,7 +315,7 @@ function callTool(name, args) {
|
|
|
313
315
|
case "meridian_get_screenshot":
|
|
314
316
|
return getScreenshot(String(args.name ?? ""));
|
|
315
317
|
case "meridian_get_component":
|
|
316
|
-
return getComponent(String(args.name ?? ""));
|
|
318
|
+
return Array.isArray(args.names) ? args.names.map((n) => getComponent(String(n))) : getComponent(String(args.name ?? ""));
|
|
317
319
|
case "meridian_get_examples":
|
|
318
320
|
return getComponentExamples(
|
|
319
321
|
String(args.name ?? ""),
|
|
@@ -325,22 +327,15 @@ function callTool(name, args) {
|
|
|
325
327
|
throw new Error(`Unknown tool: ${name}`);
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
|
-
var SERVER_INSTRUCTIONS = `Meridian Design System knowledge for writing React UI
|
|
330
|
+
var SERVER_INSTRUCTIONS = `Meridian Design System knowledge for writing React UI with @carto/meridian-ds.
|
|
329
331
|
|
|
330
|
-
REQUIRED FIRST STEP: before writing or editing
|
|
332
|
+
REQUIRED FIRST STEP: call meridian_get_conventions once before writing or editing any Meridian UI, and follow it for the rest of the session \u2014 the per-component data doesn't repeat those rules.
|
|
331
333
|
|
|
332
|
-
|
|
333
|
-
1. meridian_get_conventions \u2014 read first; apply throughout the session.
|
|
334
|
-
2. meridian_list_components \u2014 read the (small) catalog and pick by description + keywords. There is no search tool; match by meaning (the model already knows "picker" \u2248 "dropdown" \u2248 "select").
|
|
335
|
-
3. meridian_get_component(name) \u2014 when to use it, its decisionTree (conditions that point to a different component), and the high-value inherited MUI props worth knowing (curation.mui; some carry an aiHint). It also returns the example story ids.
|
|
336
|
-
4. meridian_get_examples(name[, storyId]) \u2014 real, copy-able example code. With no storyId: the lead usage story and the notable stories in full plus a list of the rest; pass a storyId for one story's full code.
|
|
337
|
-
5. meridian_list_icons \u2014 when you need an icon, list the custom icons and match by name/keyword; import from '@carto/meridian-ds/custom-icons' and render as <Name />.
|
|
338
|
-
6. meridian_get_tokens \u2014 exact palette/typography/spacing/shape/breakpoint/shadow values to use instead of hardcoding (the conventions say use tokens; this gives the values).
|
|
339
|
-
7. meridian_get_screenshot \u2014 a labeled reference image (name='icons' \u2192 every icon); Read the returned path to visually confirm a shortlisted icon or match one from a design (image-capable runtimes only).
|
|
334
|
+
Then: meridian_list_components to pick \u2192 meridian_get_component (\`names: [...]\` for several; includes the lead example) \u2192 meridian_get_examples only for other stories. Icons: meridian_list_icons. Exact token values: meridian_get_tokens.
|
|
340
335
|
|
|
341
|
-
|
|
336
|
+
Component tiers \u2014 all real and importable; prefer any of them over a bare MUI equivalent: CURATED (most) have full guidance; LISTED (\`documented: false\`, with stories) have no curation \u2014 use the examples + .d.ts; KNOWN (\`documented: false\`, no stories) have only the .d.ts.
|
|
342
337
|
|
|
343
|
-
|
|
338
|
+
Own props, types and JSDoc ship as TypeScript declarations: find node_modules/@carto/meridian-ds/dist/types -name '<Component>.d.ts'. Import components from '@carto/meridian-ds/components'.`;
|
|
344
339
|
function send(msg) {
|
|
345
340
|
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
346
341
|
}
|
|
@@ -365,7 +360,7 @@ function handle(req) {
|
|
|
365
360
|
case "tools/call":
|
|
366
361
|
try {
|
|
367
362
|
const result = callTool(params?.name, params?.arguments ?? {});
|
|
368
|
-
const text = typeof result === "string" ? result : JSON.stringify(result
|
|
363
|
+
const text = typeof result === "string" ? result : JSON.stringify(result);
|
|
369
364
|
return reply({ content: [{ type: "text", text }] });
|
|
370
365
|
} catch (err) {
|
|
371
366
|
return reply({
|
package/dist/conventions.md
CHANGED
|
@@ -15,6 +15,18 @@ Meridian is the source of truth: when an existing codebase pattern contradicts M
|
|
|
15
15
|
- Resolve "which component" by reading each one's `description` + `keywords`,
|
|
16
16
|
then confirm with its `decisionTree` (the conditions that point elsewhere) and `limitations` (what it lacks — check before you downgrade to it).
|
|
17
17
|
Don't infer selection from prop lists.
|
|
18
|
+
- Common hand-rolled patterns and their Meridian replacement:
|
|
19
|
+
- `<a target="_blank">` → `Link external`
|
|
20
|
+
- modal built from `Box` → `Dialog` + `DialogHeader`/`DialogContent`/`DialogFooter`
|
|
21
|
+
- pill/badge `Box` → `Tag`
|
|
22
|
+
- custom select/dropdown → `SelectField`, or `FilterDropdown` for filters
|
|
23
|
+
- label + required/optional mark → `LabelWithIndicator`
|
|
24
|
+
- read-only input with a copy button → `CopiableInputText`
|
|
25
|
+
- custom input → `TextField` (themed by Meridian; pick its `variant`) or `SearchField`
|
|
26
|
+
- manual ellipsis + tooltip → `EllipsisWithTooltip`
|
|
27
|
+
- status/feedback `Box` → `Alert`
|
|
28
|
+
- form label/help text → `InputLabel`/`FormHelperText` (themed by Meridian)
|
|
29
|
+
- Compose before customizing: a cluster of `styled()` wrappers that mirrors a composite is a smell (e.g. a hand-built row instead of `ListItem` + `ListItemIcon iconProps` + `ListItemText` + `ListItemRightContent`). Don't trust a code comment claiming the design system can't do something — check the component's props first.
|
|
18
30
|
|
|
19
31
|
## Icons
|
|
20
32
|
|
|
@@ -26,6 +38,7 @@ Meridian is the source of truth: when an existing codebase pattern contradicts M
|
|
|
26
38
|
- Use the standard icon sizes — small 12px, medium (default) 18px, large 24px;
|
|
27
39
|
a value off this scale (e.g. 16, 20) needs design sign-off. Prefer the
|
|
28
40
|
outlined variants.
|
|
41
|
+
- `IconWrapper`'s `size` is the outer footprint (steps 12/18/24/32/40/48), not the glyph size — the glyph inside is sized for you.
|
|
29
42
|
- A new SVG must inherit, not bake in: set `fill='currentColor'` so it takes
|
|
30
43
|
the parent's color, and use the standard size — don't hardcode
|
|
31
44
|
color/opacity/dimensions.
|
|
@@ -39,22 +52,35 @@ Meridian is the source of truth: when an existing codebase pattern contradicts M
|
|
|
39
52
|
- Don't disable a control without telling the user why — pair a disabled action with a tooltip explaining the requirement.
|
|
40
53
|
- An `aria-label` must be human-readable text describing the control, not a test id in disguise — use a separate test hook for that.
|
|
41
54
|
- User-facing strings (labels, `aria-label`, placeholders, messages) go through the app's i18n layer, not hardcoded literals.
|
|
55
|
+
- Interactive elements are real controls (`Button`, `IconButton`, `Link`, …) — never a `div`/`Box` with `onClick`.
|
|
56
|
+
- Every input has a visible or accessible label, not just a placeholder; every image has `alt`.
|
|
42
57
|
|
|
43
58
|
## Props
|
|
44
59
|
|
|
45
60
|
- Two sources, both pinned to the installed `@carto/meridian-ds`: **exact props / types / JSDoc** come from the package's **type declarations** — read the `<Name>.d.ts` directly — while the **`meridian-design-system` MCP** (`meridian_*` tools) covers what types can't: which component, when, gotchas, and real examples.
|
|
46
61
|
- A component's curation lists the high-value inherited MUI props worth knowing (`curation.mui`, e.g. `color`/`variant`/`size`); the rest of MUI's surface is available but rarely needed.
|
|
47
62
|
- Don't use `sx`/inline styles to override a Meridian component's visual design (colors, typography, borders, sizing) — use its documented props and the theme tokens. (`sx` for layout/spacing on primitives is fine — see Styling.)
|
|
63
|
+
- Prefer the prop over an override (`Typography noWrap`/`weight`, `fullWidth`, `Alert isSticky`, …). Don't restyle one component to imitate another or to fake a state — pick the component/variant that already looks that way (e.g. `TextField variant="standard"`, or plain `Typography` for a read-only value). Avoid brittle descendant selectors into a component's internals.
|
|
64
|
+
- Don't restate or cancel built-in styling: no wrapper re-applying a component's own border/radius/background, no `disablePadding` on a parent only to re-add padding on its children.
|
|
65
|
+
- `!important` on a Meridian or themed component is an override smell — a design-system gap to raise upstream.
|
|
48
66
|
- If no prop covers the visual change you need, treat it as a design-system gap: request the variant/behavior upstream in Meridian rather than permanently overriding the component locally — a local `styled()` of a Meridian component is a smell, not a fix.
|
|
49
67
|
|
|
50
68
|
## Styling
|
|
51
69
|
|
|
52
|
-
- Use theme tokens, never hardcoded values: colors from the palette (`text.*`, `background.*`, `divider`, semantic `*.main`) and spacing from the theme
|
|
53
|
-
-
|
|
70
|
+
- Use theme tokens, never hardcoded values: colors from the palette (`text.*`, `background.*`, `divider`, semantic `*.main`) and spacing from the theme scale (`theme.spacing(n)`, or `sx` numeric spacing like `px: 2` / `mt: 1.5`) — no raw hex/rgb or px. Margin, padding and offsets use steps `0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 4, 5, 6, 7, 8, 9, 12, 15` (2–120px); `width`/`height` may use any step (`spacing-step` in `@carto/meridian-ds/eslint-rules` lints it). Get the exact values — palette keys, spacing base, type ramp, `shape.borderRadius`, breakpoints, shadows — from `meridian_get_tokens`.
|
|
71
|
+
- Token details:
|
|
72
|
+
- `sx` spacing keys (`m*`/`p*`/`gap`) resolve to the scale, but `top`/`right`/`bottom`/`left` don't (`top: 2` is 2px) — use `theme.spacing()` there.
|
|
73
|
+
- In `styled()`, always use `theme.spacing()`. `style={}` isn't theme-aware — use `sx`.
|
|
74
|
+
- Borders use `divider`; radius comes from `shape.borderRadius`; elevation from `theme.shadows[n]`.
|
|
75
|
+
- Translucent black/white use `palette.black[N]`/`palette.white[N]` (N = 90, 60, 40, 25, 12, 8, 4), not `alpha()`.
|
|
76
|
+
- Motion uses `theme.transitions` and respects `prefers-reduced-motion`; responsive rules use `theme.breakpoints`.
|
|
77
|
+
- Text uses `<Typography variant=…>` (with the `weight` prop for emphasis), never hardcoded `fontSize`/`fontWeight`/`lineHeight`/`fontFamily`. Text that can overflow gets `EllipsisWithTooltip` (or `noWrap` when the full text isn't needed).
|
|
54
78
|
- Meridian's type scale is custom and does not match MUI defaults (e.g. `body2`/`caption` differ from MUI's sizes, and there are Meridian-only variants like `code1`/`code2`/`code3` and `overlineDelicate`). Pick the variant by role from Typography's variant list (`meridian_get_examples('Typography')`) — don't assume a px size or map px → variant yourself.
|
|
55
79
|
- `sx` is the right tool for one-off layout/spacing on primitives (`Box`/`Grid`/`Stack`), not the deprecated MUI system props
|
|
56
80
|
(`<Box px={2}>` → `<Box sx={{ px: 2 }}>`). This is layout, distinct from restyling a Meridian component (see Props).
|
|
57
81
|
|
|
82
|
+
- A reusable component doesn't bake in its placement: no absolute positioning, outer margin, or fixed outer size — accept `sx` and let the caller place it.
|
|
83
|
+
|
|
58
84
|
## Examples
|
|
59
85
|
|
|
60
86
|
- Never invent example code when a story exists — use the component's real story source (`meridian_get_examples`). The lead Usage story is the copy-paste starting point; the later stories are illustrative (variants, edge cases) and may carry story-arg shells, so don't copy one wholesale as your baseline.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const spacingSteps = require("../spacing-steps-DOjPPOKL.cjs");
|
|
4
|
+
const escape = (step) => String(step).replace(".", "\\.");
|
|
5
|
+
const STEP = `(0|${spacingSteps.SPACING_STEPS.map(escape).join("|")})`;
|
|
6
|
+
const OFF_SCALE = `Literal[raw=/^[0-9.]+$/][raw!=/^${STEP}$/]`;
|
|
7
|
+
const NON_ZERO = "Literal[raw=/^[0-9.]+$/][raw!=/^0+(\\.0+)?$/]";
|
|
8
|
+
const SPACING_KEY = "/^([mp][trblxy]?|(margin|padding)(Top|Right|Bottom|Left|X|Y|Inline|Block)?|gap|rowGap|columnGap)$/";
|
|
9
|
+
const OFFSET_KEY = "/^(top|right|bottom|left|inset)$/";
|
|
10
|
+
const SPACING_OR_OFFSET_KEY = "/^([mp][trblxy]?|(margin|padding)(Top|Right|Bottom|Left|X|Y|Inline|Block)?|gap|rowGap|columnGap|top|right|bottom|left|inset)$/";
|
|
11
|
+
const SX = ":matches(JSXAttribute[name.name='sx'], Property[key.name='sx'])";
|
|
12
|
+
const STYLED_OR_STYLE = ":matches(CallExpression[callee.callee.name='styled'], CallExpression[callee.object.name='styled'], CallExpression[callee.callee.object.name='styled'], JSXAttribute[name.name='style'])";
|
|
13
|
+
const SPACING_CALL = "CallExpression[callee.property.name='spacing']";
|
|
14
|
+
const anyOf = (...selectors) => selectors.join(", ");
|
|
15
|
+
const meridianDesignRules = {
|
|
16
|
+
"spacing-step": {
|
|
17
|
+
selector: anyOf(
|
|
18
|
+
`${SX} Property[key.name=${SPACING_KEY}] > ${OFF_SCALE}`,
|
|
19
|
+
`${SX} Property[key.name=${SPACING_KEY}] > UnaryExpression > ${OFF_SCALE}`,
|
|
20
|
+
`${SX} Property[key.name=${SPACING_KEY}] > ArrayExpression > ${OFF_SCALE}`,
|
|
21
|
+
`${SX} Property[key.name=${SPACING_KEY}] > ObjectExpression > Property > ${OFF_SCALE}`,
|
|
22
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > ${SPACING_CALL} > ${OFF_SCALE}`,
|
|
23
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > ${SPACING_CALL} > UnaryExpression > ${OFF_SCALE}`,
|
|
24
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > TemplateLiteral > ${SPACING_CALL} > ${OFF_SCALE}`
|
|
25
|
+
),
|
|
26
|
+
message: `Spacing off the Meridian scale: margin, padding, gap and offsets use ${spacingSteps.SPACING_STEPS.join(", ")}.`
|
|
27
|
+
},
|
|
28
|
+
"px-spacing": {
|
|
29
|
+
selector: anyOf(
|
|
30
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > Literal[value=/^-?\\d+(\\.\\d+)?px/]`,
|
|
31
|
+
`${STYLED_OR_STYLE} Property[key.name=${SPACING_OR_OFFSET_KEY}] > ${NON_ZERO}`,
|
|
32
|
+
`${STYLED_OR_STYLE} Property[key.name=${SPACING_OR_OFFSET_KEY}] > UnaryExpression > ${NON_ZERO}`,
|
|
33
|
+
`${SX} Property[key.name=${OFFSET_KEY}] > ${NON_ZERO}`,
|
|
34
|
+
`${SX} Property[key.name=${OFFSET_KEY}] > UnaryExpression > ${NON_ZERO}`
|
|
35
|
+
),
|
|
36
|
+
message: "Raw px spacing: use the theme scale — `sx` spacing numbers (`px: 2`) or `theme.spacing(n)`. Numbers in `styled()`/`style` and `sx` offsets are px."
|
|
37
|
+
},
|
|
38
|
+
"filled-icon": {
|
|
39
|
+
selector: "ImportDeclaration[source.value=/^@mui.icons-material.(?!.*(Outlined|Rounded|Sharp|TwoTone)$)/]",
|
|
40
|
+
message: "Filled MUI icon: import the Outlined variant."
|
|
41
|
+
},
|
|
42
|
+
"alpha-white-black": {
|
|
43
|
+
selector: "CallExpression[callee.name='alpha'] > MemberExpression[property.name=/^(white|black)$/][object.property.name='common']",
|
|
44
|
+
message: "`alpha()` on white/black: use the palette opacity shades `white[N]` / `black[N]`."
|
|
45
|
+
},
|
|
46
|
+
"hex-color": {
|
|
47
|
+
selector: "Literal[value=/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/]",
|
|
48
|
+
message: "Hardcoded hex color: use a palette token."
|
|
49
|
+
},
|
|
50
|
+
"rgb-color": {
|
|
51
|
+
selector: "Literal[value=/^rgba?\\(/]",
|
|
52
|
+
message: "Hardcoded rgb()/rgba() color: use a palette token."
|
|
53
|
+
},
|
|
54
|
+
"typography-style": {
|
|
55
|
+
selector: `:matches(JSXAttribute[name.name=/^(sx|style)$/], CallExpression[callee.callee.name='styled'], CallExpression[callee.callee.object.name='styled']) Property[key.name=/^(fontSize|fontWeight|lineHeight|fontFamily)$/]`,
|
|
56
|
+
message: "Hardcoded typography: use `<Typography variant>` (and `weight`), or the theme typography tokens."
|
|
57
|
+
},
|
|
58
|
+
"external-link": {
|
|
59
|
+
selector: "JSXOpeningElement[name.name='a'] > JSXAttribute[name.name='target'] > Literal[value='_blank']",
|
|
60
|
+
message: 'Raw `<a target="_blank">`: use Meridian `Link` with `external`.'
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
exports.meridianDesignRules = meridianDesignRules;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { S as SPACING_STEPS } from "../spacing-steps-CN4lgz20.js";
|
|
2
|
+
const escape = (step) => String(step).replace(".", "\\.");
|
|
3
|
+
const STEP = `(0|${SPACING_STEPS.map(escape).join("|")})`;
|
|
4
|
+
const OFF_SCALE = `Literal[raw=/^[0-9.]+$/][raw!=/^${STEP}$/]`;
|
|
5
|
+
const NON_ZERO = "Literal[raw=/^[0-9.]+$/][raw!=/^0+(\\.0+)?$/]";
|
|
6
|
+
const SPACING_KEY = "/^([mp][trblxy]?|(margin|padding)(Top|Right|Bottom|Left|X|Y|Inline|Block)?|gap|rowGap|columnGap)$/";
|
|
7
|
+
const OFFSET_KEY = "/^(top|right|bottom|left|inset)$/";
|
|
8
|
+
const SPACING_OR_OFFSET_KEY = "/^([mp][trblxy]?|(margin|padding)(Top|Right|Bottom|Left|X|Y|Inline|Block)?|gap|rowGap|columnGap|top|right|bottom|left|inset)$/";
|
|
9
|
+
const SX = ":matches(JSXAttribute[name.name='sx'], Property[key.name='sx'])";
|
|
10
|
+
const STYLED_OR_STYLE = ":matches(CallExpression[callee.callee.name='styled'], CallExpression[callee.object.name='styled'], CallExpression[callee.callee.object.name='styled'], JSXAttribute[name.name='style'])";
|
|
11
|
+
const SPACING_CALL = "CallExpression[callee.property.name='spacing']";
|
|
12
|
+
const anyOf = (...selectors) => selectors.join(", ");
|
|
13
|
+
const meridianDesignRules = {
|
|
14
|
+
"spacing-step": {
|
|
15
|
+
selector: anyOf(
|
|
16
|
+
`${SX} Property[key.name=${SPACING_KEY}] > ${OFF_SCALE}`,
|
|
17
|
+
`${SX} Property[key.name=${SPACING_KEY}] > UnaryExpression > ${OFF_SCALE}`,
|
|
18
|
+
`${SX} Property[key.name=${SPACING_KEY}] > ArrayExpression > ${OFF_SCALE}`,
|
|
19
|
+
`${SX} Property[key.name=${SPACING_KEY}] > ObjectExpression > Property > ${OFF_SCALE}`,
|
|
20
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > ${SPACING_CALL} > ${OFF_SCALE}`,
|
|
21
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > ${SPACING_CALL} > UnaryExpression > ${OFF_SCALE}`,
|
|
22
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > TemplateLiteral > ${SPACING_CALL} > ${OFF_SCALE}`
|
|
23
|
+
),
|
|
24
|
+
message: `Spacing off the Meridian scale: margin, padding, gap and offsets use ${SPACING_STEPS.join(", ")}.`
|
|
25
|
+
},
|
|
26
|
+
"px-spacing": {
|
|
27
|
+
selector: anyOf(
|
|
28
|
+
`Property[key.name=${SPACING_OR_OFFSET_KEY}] > Literal[value=/^-?\\d+(\\.\\d+)?px/]`,
|
|
29
|
+
`${STYLED_OR_STYLE} Property[key.name=${SPACING_OR_OFFSET_KEY}] > ${NON_ZERO}`,
|
|
30
|
+
`${STYLED_OR_STYLE} Property[key.name=${SPACING_OR_OFFSET_KEY}] > UnaryExpression > ${NON_ZERO}`,
|
|
31
|
+
`${SX} Property[key.name=${OFFSET_KEY}] > ${NON_ZERO}`,
|
|
32
|
+
`${SX} Property[key.name=${OFFSET_KEY}] > UnaryExpression > ${NON_ZERO}`
|
|
33
|
+
),
|
|
34
|
+
message: "Raw px spacing: use the theme scale — `sx` spacing numbers (`px: 2`) or `theme.spacing(n)`. Numbers in `styled()`/`style` and `sx` offsets are px."
|
|
35
|
+
},
|
|
36
|
+
"filled-icon": {
|
|
37
|
+
selector: "ImportDeclaration[source.value=/^@mui.icons-material.(?!.*(Outlined|Rounded|Sharp|TwoTone)$)/]",
|
|
38
|
+
message: "Filled MUI icon: import the Outlined variant."
|
|
39
|
+
},
|
|
40
|
+
"alpha-white-black": {
|
|
41
|
+
selector: "CallExpression[callee.name='alpha'] > MemberExpression[property.name=/^(white|black)$/][object.property.name='common']",
|
|
42
|
+
message: "`alpha()` on white/black: use the palette opacity shades `white[N]` / `black[N]`."
|
|
43
|
+
},
|
|
44
|
+
"hex-color": {
|
|
45
|
+
selector: "Literal[value=/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/]",
|
|
46
|
+
message: "Hardcoded hex color: use a palette token."
|
|
47
|
+
},
|
|
48
|
+
"rgb-color": {
|
|
49
|
+
selector: "Literal[value=/^rgba?\\(/]",
|
|
50
|
+
message: "Hardcoded rgb()/rgba() color: use a palette token."
|
|
51
|
+
},
|
|
52
|
+
"typography-style": {
|
|
53
|
+
selector: `:matches(JSXAttribute[name.name=/^(sx|style)$/], CallExpression[callee.callee.name='styled'], CallExpression[callee.callee.object.name='styled']) Property[key.name=/^(fontSize|fontWeight|lineHeight|fontFamily)$/]`,
|
|
54
|
+
message: "Hardcoded typography: use `<Typography variant>` (and `weight`), or the theme typography tokens."
|
|
55
|
+
},
|
|
56
|
+
"external-link": {
|
|
57
|
+
selector: "JSXOpeningElement[name.name='a'] > JSXAttribute[name.name='target'] > Literal[value='_blank']",
|
|
58
|
+
message: 'Raw `<a target="_blank">`: use Meridian `Link` with `external`.'
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
export {
|
|
62
|
+
meridianDesignRules
|
|
63
|
+
};
|