@clearstory/drywall-react 7.1.0 → 7.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.
@@ -1,9 +1,13 @@
1
- import e from "./no-system-props.js";
2
- import l from "./prefer-vars-palette.js";
1
+ import e from "./no-shorthand-spacing.js";
2
+ import l from "./no-system-props.js";
3
+ import a from "./prefer-logical-properties.js";
4
+ import o from "./prefer-vars-palette.js";
3
5
  const r = {
4
6
  rules: {
5
- "no-system-props": e,
6
- "prefer-vars-palette": l
7
+ "no-shorthand-spacing": e,
8
+ "no-system-props": l,
9
+ "prefer-logical-properties": a,
10
+ "prefer-vars-palette": o
7
11
  },
8
12
  configs: {}
9
13
  };
@@ -13,21 +17,25 @@ r.configs.recommended = {
13
17
  "@clearstory/drywall-react": r
14
18
  },
15
19
  rules: {
20
+ "@clearstory/drywall-react/no-shorthand-spacing": "error",
16
21
  "@clearstory/drywall-react/no-system-props": "error",
22
+ "@clearstory/drywall-react/prefer-logical-properties": "error",
17
23
  "@clearstory/drywall-react/prefer-vars-palette": "error"
18
24
  }
19
25
  };
20
- const t = {
26
+ const y = {
21
27
  name: "@clearstory/drywall-react",
22
28
  plugins: {
23
29
  "@clearstory/drywall-react": r
24
30
  },
25
31
  rules: {
32
+ "@clearstory/drywall-react/no-shorthand-spacing": "error",
26
33
  "@clearstory/drywall-react/no-system-props": "error",
34
+ "@clearstory/drywall-react/prefer-logical-properties": "error",
27
35
  "@clearstory/drywall-react/prefer-vars-palette": "error"
28
36
  }
29
37
  };
30
38
  export {
31
- t as default,
39
+ y as default,
32
40
  r as plugin
33
41
  };
@@ -0,0 +1,3 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,57 @@
1
+ import { createSxVisitor as i } from "./sx-utils.js";
2
+ const s = {
3
+ p: "padding",
4
+ pt: "paddingTop",
5
+ pr: "paddingRight",
6
+ pb: "paddingBottom",
7
+ pl: "paddingLeft",
8
+ px: "paddingX",
9
+ py: "paddingY",
10
+ m: "margin",
11
+ mt: "marginTop",
12
+ mr: "marginRight",
13
+ mb: "marginBottom",
14
+ ml: "marginLeft",
15
+ mx: "marginX",
16
+ my: "marginY"
17
+ }, g = {
18
+ meta: {
19
+ type: "suggestion",
20
+ fixable: "code",
21
+ docs: {
22
+ description: "Disallow MUI spacing shorthands (p, pt, m, mx, etc.) in sx props",
23
+ category: "Best Practices",
24
+ recommended: !0
25
+ },
26
+ messages: {
27
+ shorthandSpacing: 'Use "{{replacement}}" instead of shorthand "{{property}}" in sx for readability.'
28
+ },
29
+ schema: []
30
+ },
31
+ create(a) {
32
+ return i({
33
+ onProperty(n, e) {
34
+ const t = s[n];
35
+ t && a.report({
36
+ node: e,
37
+ messageId: "shorthandSpacing",
38
+ data: { property: n, replacement: t },
39
+ fix(p) {
40
+ const r = e;
41
+ if (e.type === "Literal") {
42
+ const o = a.sourceCode.getText(r)[0];
43
+ return p.replaceText(
44
+ r,
45
+ `${o}${t}${o}`
46
+ );
47
+ }
48
+ return p.replaceText(r, t);
49
+ }
50
+ });
51
+ }
52
+ });
53
+ }
54
+ };
55
+ export {
56
+ g as default
57
+ };
@@ -122,10 +122,28 @@ const g = [
122
122
  "typography",
123
123
  // other common system props
124
124
  "spacing"
125
- ], d = ["Box", "Stack", "Typography", "Link"], u = {
125
+ ], u = {
126
+ // color: semantic theme colors (primary, secondary, error, etc.)
126
127
  Typography: ["color"],
127
- // semantic colors only
128
- Link: ["color"]
128
+ Link: ["color"],
129
+ Button: ["color"],
130
+ ButtonGroup: ["color"],
131
+ IconButton: ["color"],
132
+ Chip: ["color"],
133
+ Badge: ["color"],
134
+ Alert: ["color"],
135
+ CircularProgress: ["color"],
136
+ LinearProgress: ["color"],
137
+ Radio: ["color"],
138
+ Checkbox: ["color"],
139
+ Switch: ["color"],
140
+ SvgIcon: ["color"],
141
+ FormLabel: ["color"],
142
+ FormControl: ["color"],
143
+ ToggleButton: ["color"],
144
+ ToggleButtonGroup: ["color"],
145
+ // Container: maxWidth accepts breakpoint values (xs, sm, md, lg, xl), not CSS values
146
+ Container: ["maxWidth"]
129
147
  }, h = {
130
148
  meta: {
131
149
  type: "problem",
@@ -139,52 +157,55 @@ const g = [
139
157
  },
140
158
  schema: []
141
159
  },
142
- create(n) {
143
- const o = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Set();
160
+ create(t) {
161
+ const o = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Set();
144
162
  return {
145
163
  ImportDeclaration(l) {
146
164
  const r = l;
147
165
  r.source.value === "@clearstory/drywall-react" && r.specifiers.forEach((e) => {
148
- e.type === "ImportSpecifier" && e.imported && d.includes(e.imported.name) ? o.add(e.local.name) : e.type === "ImportNamespaceSpecifier" && s.add(e.local.name);
166
+ e.type === "ImportSpecifier" && e.imported ? o.set(e.local.name, e.imported.name) : e.type === "ImportNamespaceSpecifier" && p.add(e.local.name);
149
167
  });
150
168
  },
151
169
  JSXOpeningElement(l) {
152
170
  const r = l;
153
- let e;
171
+ let e, s;
154
172
  if ("name" in r.name) {
155
173
  if (e = r.name.name, !o.has(e))
156
174
  return;
175
+ s = o.get(e);
157
176
  } else if (r.name.type === "JSXMemberExpression") {
158
- const t = r.name.object.name;
159
- if (e = r.name.property.name, !s.has(t) || !d.includes(e))
177
+ const n = r.name.object.name;
178
+ if (e = r.name.property.name, s = e, !p.has(n))
160
179
  return;
161
180
  } else
162
181
  return;
163
- const p = [];
164
- r.attributes.forEach((t) => {
165
- const i = t;
182
+ const m = [];
183
+ r.attributes.forEach((n) => {
184
+ const i = n;
166
185
  if (i.type !== "JSXAttribute" || !i.name)
167
186
  return;
168
187
  const a = i.name.name;
169
188
  if (a === "sx" || !g.includes(a))
170
189
  return;
171
- const m = u[e];
172
- if (m && m.includes(a))
173
- if (a === "color" && i.value) {
174
- const c = f(i.value);
175
- if (y(c))
176
- return;
190
+ const d = u[s];
191
+ if (d && d.includes(a))
192
+ if (a === "color") {
193
+ if (i.value) {
194
+ const c = f(i.value);
195
+ if (y(c))
196
+ return;
197
+ }
177
198
  } else
178
199
  return;
179
- p.push(
200
+ m.push(
180
201
  i
181
202
  );
182
- }), p.forEach((t) => {
183
- n.report({
184
- node: t,
203
+ }), m.forEach((n) => {
204
+ t.report({
205
+ node: n,
185
206
  messageId: "systemProp",
186
207
  data: {
187
- prop: t.name.name,
208
+ prop: n.name.name,
188
209
  component: e
189
210
  }
190
211
  });
@@ -193,9 +214,9 @@ const g = [
193
214
  };
194
215
  }
195
216
  };
196
- function f(n) {
197
- if (!n) return null;
198
- const o = n;
217
+ function f(t) {
218
+ if (!t) return null;
219
+ const o = t;
199
220
  if (o.type === "Literal")
200
221
  return String(o.value);
201
222
  if (o.type === "JSXExpressionContainer" && o.expression) {
@@ -206,7 +227,7 @@ function f(n) {
206
227
  }
207
228
  return null;
208
229
  }
209
- function y(n) {
230
+ function y(t) {
210
231
  return [
211
232
  "inherit",
212
233
  "primary",
@@ -214,8 +235,16 @@ function y(n) {
214
235
  "error",
215
236
  "warning",
216
237
  "info",
217
- "success"
218
- ].includes(n || "");
238
+ "success",
239
+ "default",
240
+ // Badge, Radio, Checkbox, Switch
241
+ "action",
242
+ // SvgIcon
243
+ "disabled",
244
+ // SvgIcon
245
+ "standard"
246
+ // ToggleButton, ToggleButtonGroup
247
+ ].includes(t || "");
219
248
  }
220
249
  export {
221
250
  h as default
@@ -0,0 +1,3 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,72 @@
1
+ import { createSxVisitor as d } from "./sx-utils.js";
2
+ const l = {
3
+ // Positioning (inset)
4
+ top: "insetBlockStart",
5
+ bottom: "insetBlockEnd",
6
+ left: "insetInlineStart",
7
+ right: "insetInlineEnd",
8
+ // Padding
9
+ paddingTop: "paddingBlockStart",
10
+ paddingBottom: "paddingBlockEnd",
11
+ paddingLeft: "paddingInlineStart",
12
+ paddingRight: "paddingInlineEnd",
13
+ paddingX: "paddingInline",
14
+ paddingY: "paddingBlock",
15
+ // Margin
16
+ marginTop: "marginBlockStart",
17
+ marginBottom: "marginBlockEnd",
18
+ marginLeft: "marginInlineStart",
19
+ marginRight: "marginInlineEnd",
20
+ marginX: "marginInline",
21
+ marginY: "marginBlock",
22
+ // Border
23
+ borderTop: "borderBlockStart",
24
+ borderBottom: "borderBlockEnd",
25
+ borderLeft: "borderInlineStart",
26
+ borderRight: "borderInlineEnd",
27
+ // Border color
28
+ borderTopColor: "borderBlockStartColor",
29
+ borderBottomColor: "borderBlockEndColor",
30
+ borderLeftColor: "borderInlineStartColor",
31
+ borderRightColor: "borderInlineEndColor"
32
+ }, g = {
33
+ meta: {
34
+ type: "suggestion",
35
+ fixable: "code",
36
+ docs: {
37
+ description: "Enforce CSS logical properties instead of physical direction properties in sx props",
38
+ category: "Best Practices",
39
+ recommended: !0
40
+ },
41
+ messages: {
42
+ physicalProperty: 'Use logical property "{{replacement}}" instead of "{{property}}" in sx for RTL support.'
43
+ },
44
+ schema: []
45
+ },
46
+ create(t) {
47
+ return d({
48
+ onProperty(n, o) {
49
+ const r = l[n];
50
+ r && t.report({
51
+ node: o,
52
+ messageId: "physicalProperty",
53
+ data: { property: n, replacement: r },
54
+ fix(i) {
55
+ const e = o;
56
+ if (o.type === "Literal") {
57
+ const a = t.sourceCode.getText(e)[0];
58
+ return i.replaceText(
59
+ e,
60
+ `${a}${r}${a}`
61
+ );
62
+ }
63
+ return i.replaceText(e, r);
64
+ }
65
+ });
66
+ }
67
+ });
68
+ }
69
+ };
70
+ export {
71
+ g as default
72
+ };
@@ -1,4 +1,5 @@
1
- const b = /* @__PURE__ */ new Set([
1
+ import { createSxVisitor as l } from "./sx-utils.js";
2
+ const f = /* @__PURE__ */ new Set([
2
3
  "color",
3
4
  "backgroundColor",
4
5
  "bgcolor",
@@ -22,7 +23,7 @@ const b = /* @__PURE__ */ new Set([
22
23
  "initial",
23
24
  "unset",
24
25
  "none"
25
- ]), C = {
26
+ ]), d = {
26
27
  meta: {
27
28
  type: "suggestion",
28
29
  docs: {
@@ -36,155 +37,94 @@ const b = /* @__PURE__ */ new Set([
36
37
  },
37
38
  schema: []
38
39
  },
39
- create(t) {
40
- return {
41
- JSXOpeningElement(e) {
42
- const r = e.attributes.find(
43
- (s) => s.type === "JSXAttribute" && s.name?.name === "sx"
44
- );
45
- if (!r) return;
46
- const o = r.value;
47
- if (o && o.type === "JSXExpressionContainer") {
48
- const s = o.expression;
49
- y(t, s);
50
- }
40
+ create(s) {
41
+ return l({
42
+ onProperty(t, r, e) {
43
+ f.has(t) && i(s, t, e);
44
+ },
45
+ onCallback(t) {
46
+ c(s, t);
51
47
  }
52
- };
48
+ });
53
49
  }
54
50
  };
55
- function y(t, e) {
56
- if (e)
57
- switch (e.type) {
58
- case "ObjectExpression":
59
- u(t, e);
60
- break;
61
- case "ArrowFunctionExpression":
62
- case "FunctionExpression": {
63
- d(t, e);
64
- const n = g(e);
65
- n && u(t, n);
66
- break;
67
- }
68
- case "ArrayExpression": {
69
- e.elements.forEach((r) => {
70
- r && y(t, r);
71
- });
72
- break;
73
- }
74
- }
75
- }
76
- function u(t, e) {
77
- e.properties.forEach((r) => {
78
- if (r.type !== "Property") return;
79
- const o = r, s = m(o.key, o.computed);
80
- if (!s) return;
81
- const i = o.value;
82
- if (i.type === "ObjectExpression") {
83
- u(t, i);
84
- return;
85
- }
86
- if (b.has(s)) {
87
- if (i.type === "ArrowFunctionExpression" || i.type === "FunctionExpression") {
88
- d(t, i);
89
- return;
90
- }
91
- if (i.type === "Literal") {
92
- const a = i.value;
93
- typeof a == "string" && !p.has(a) && t.report({
94
- node: i,
95
- messageId: "stringColor",
96
- data: { value: a, property: s }
97
- });
98
- return;
99
- }
100
- if (i.type === "TemplateLiteral") {
101
- const a = i;
102
- if (a.expressions.length === 0) {
103
- const c = a.quasis[0]?.value.raw;
104
- c && !p.has(c) && t.report({
105
- node: i,
106
- messageId: "stringColor",
107
- data: { value: c, property: s }
108
- });
109
- }
110
- return;
111
- }
112
- if (i.type === "ConditionalExpression") {
113
- const a = i;
114
- l(t, a.consequent, s), l(t, a.alternate, s);
115
- }
51
+ function i(s, t, r) {
52
+ if (r.type === "ObjectExpression") {
53
+ const e = r.properties;
54
+ for (const o of e) {
55
+ if (o.type === "SpreadElement" || o.type !== "Property") continue;
56
+ i(s, t, o.value);
116
57
  }
117
- });
118
- }
119
- function l(t, e, n) {
120
- if (e.type === "Literal") {
121
- const r = e.value;
122
- typeof r == "string" && !p.has(r) && t.report({
123
- node: e,
58
+ return;
59
+ }
60
+ if (r.type === "ArrowFunctionExpression" || r.type === "FunctionExpression") {
61
+ c(s, r);
62
+ return;
63
+ }
64
+ if (r.type === "Literal") {
65
+ const e = r.value;
66
+ typeof e == "string" && !p.has(e) && s.report({
67
+ node: r,
124
68
  messageId: "stringColor",
125
- data: { value: r, property: n }
69
+ data: { value: e, property: t }
126
70
  });
127
- } else if (e.type === "ConditionalExpression") {
128
- const r = e;
129
- l(t, r.consequent, n), l(t, r.alternate, n);
71
+ return;
72
+ }
73
+ if (r.type === "TemplateLiteral") {
74
+ const e = r;
75
+ if (e.expressions.length === 0) {
76
+ const o = e.quasis[0]?.value.raw;
77
+ o && !p.has(o) && s.report({
78
+ node: r,
79
+ messageId: "stringColor",
80
+ data: { value: o, property: t }
81
+ });
82
+ }
83
+ return;
84
+ }
85
+ if (r.type === "ConditionalExpression") {
86
+ const e = r;
87
+ i(s, t, e.consequent), i(s, t, e.alternate);
130
88
  }
131
89
  }
132
- function d(t, e) {
133
- const n = e.params;
134
- if (!n || n.length === 0) return;
135
- const r = n[0];
136
- if (r.type === "Identifier") {
137
- const o = r.name, s = e.body;
138
- f(t, s, o);
139
- } else r.type === "ObjectPattern" && r.properties.forEach((s) => {
140
- if (s.type !== "Property") return;
141
- s.key.name === "palette" && t.report({
142
- node: s,
90
+ function c(s, t) {
91
+ const r = t.params;
92
+ if (!r || r.length === 0) return;
93
+ const e = r[0];
94
+ if (e.type === "Identifier") {
95
+ const o = e.name, n = t.body;
96
+ a(s, n, o);
97
+ } else e.type === "ObjectPattern" && e.properties.forEach((n) => {
98
+ if (n.type !== "Property") return;
99
+ n.key.name === "palette" && s.report({
100
+ node: n,
143
101
  messageId: "useVarsPalette",
144
102
  data: { access: "destructured palette" }
145
103
  });
146
104
  });
147
105
  }
148
- function f(t, e, n) {
149
- if (!(!e || typeof e != "object")) {
150
- if (e.type === "MemberExpression") {
151
- const r = e.object, o = e.property;
152
- if (r.type === "Identifier" && r.name === n && o.name === "palette") {
153
- t.report({
154
- node: e,
106
+ function a(s, t, r) {
107
+ if (!(!t || typeof t != "object")) {
108
+ if (t.type === "MemberExpression") {
109
+ const e = t.object, o = t.property;
110
+ if (e.type === "Identifier" && e.name === r && o.name === "palette") {
111
+ s.report({
112
+ node: t,
155
113
  messageId: "useVarsPalette",
156
- data: { access: `${n}.palette` }
114
+ data: { access: `${r}.palette` }
157
115
  });
158
116
  return;
159
117
  }
160
118
  }
161
- for (const r of Object.keys(e)) {
162
- if (r === "parent") continue;
163
- const o = e[r];
164
- o && typeof o == "object" && (Array.isArray(o) ? o.forEach((s) => {
165
- s && typeof s == "object" && "type" in s && f(t, s, n);
166
- }) : "type" in o && f(t, o, n));
119
+ for (const e of Object.keys(t)) {
120
+ if (e === "parent") continue;
121
+ const o = t[e];
122
+ o && typeof o == "object" && (Array.isArray(o) ? o.forEach((n) => {
123
+ n && typeof n == "object" && "type" in n && a(s, n, r);
124
+ }) : "type" in o && a(s, o, r));
167
125
  }
168
126
  }
169
127
  }
170
- function g(t) {
171
- const e = t.body;
172
- if (e.type === "ObjectExpression")
173
- return e;
174
- if (e.type === "BlockStatement") {
175
- const n = e.body;
176
- for (const r of n)
177
- if (r.type === "ReturnStatement") {
178
- const o = r.argument;
179
- if (o?.type === "ObjectExpression")
180
- return o;
181
- }
182
- }
183
- return null;
184
- }
185
- function m(t, e) {
186
- return t.type === "Identifier" && !e ? t.name || null : t.type === "Literal" && typeof t.value == "string" ? t.value : null;
187
- }
188
128
  export {
189
- C as default
129
+ d as default
190
130
  };
@@ -0,0 +1,53 @@
1
+ import { Rule } from 'eslint';
2
+ /**
3
+ * Shared AST traversal utilities for ESLint rules that inspect MUI `sx` props.
4
+ *
5
+ * Provides a visitor pattern that:
6
+ * 1. Finds the `sx` attribute on JSXOpeningElement nodes
7
+ * 2. Traverses the expression (object, callback, or array)
8
+ * 3. Recurses into nested objects (pseudo-selectors like "&:hover")
9
+ * 4. Calls rule-specific callbacks for each property encountered
10
+ */
11
+ /**
12
+ * Minimal AST node type for traversal within ESLint rules.
13
+ */
14
+ export type ASTNode = Record<string, unknown> & {
15
+ type: string;
16
+ };
17
+ export interface SxVisitorCallbacks {
18
+ /**
19
+ * Called for each property in an sx object expression, including properties
20
+ * inside nested objects (pseudo-selectors like "&:hover").
21
+ * Recursion into nested objects is handled automatically by the visitor.
22
+ */
23
+ onProperty: (keyName: string, keyNode: ASTNode, valueNode: ASTNode) => void;
24
+ /**
25
+ * Called when the sx value (or an array element) is a callback function
26
+ * (ArrowFunctionExpression or FunctionExpression). Fires before the
27
+ * returned object's properties are visited via onProperty.
28
+ */
29
+ onCallback?: (func: ASTNode) => void;
30
+ }
31
+ /**
32
+ * Creates a JSXOpeningElement visitor that locates the `sx` attribute
33
+ * and walks its value, invoking the provided callbacks for each property.
34
+ */
35
+ export declare function createSxVisitor(callbacks: SxVisitorCallbacks): Rule.RuleListener;
36
+ /**
37
+ * Walk an sx value expression (object, callback, or array) and invoke
38
+ * the provided callbacks for each property encountered.
39
+ */
40
+ export declare function visitSxValue(node: ASTNode, callbacks: SxVisitorCallbacks): void;
41
+ /**
42
+ * Extract the returned ObjectExpression from a function/arrow function body.
43
+ */
44
+ export declare function getReturnedObjectExpression(func: ASTNode): ASTNode | null;
45
+ /**
46
+ * Get the string name of a property key.
47
+ * Returns null for computed keys or non-string keys.
48
+ */
49
+ export declare function getKeyName(key: {
50
+ type: string;
51
+ name?: string;
52
+ value?: string;
53
+ }, computed: boolean): string | null;
@@ -0,0 +1,67 @@
1
+ function f(e) {
2
+ return {
3
+ JSXOpeningElement(t) {
4
+ const n = t.attributes.find(
5
+ (i) => i.type === "JSXAttribute" && i.name?.name === "sx"
6
+ );
7
+ if (!n) return;
8
+ const r = n.value;
9
+ if (r && r.type === "JSXExpressionContainer") {
10
+ const i = r.expression;
11
+ u(i, e);
12
+ }
13
+ }
14
+ };
15
+ }
16
+ function u(e, t) {
17
+ if (e)
18
+ switch (e.type) {
19
+ case "ObjectExpression":
20
+ o(e, t);
21
+ break;
22
+ case "ArrowFunctionExpression":
23
+ case "FunctionExpression": {
24
+ t.onCallback?.(e);
25
+ const s = p(e);
26
+ s && o(s, t);
27
+ break;
28
+ }
29
+ case "ArrayExpression": {
30
+ e.elements.forEach((n) => {
31
+ n && u(n, t);
32
+ });
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ function o(e, t) {
38
+ e.properties.forEach((n) => {
39
+ if (n.type !== "Property") return;
40
+ const r = n, i = c(r.key, r.computed);
41
+ i && (r.value.type === "ObjectExpression" && o(r.value, t), t.onProperty(i, r.key, r.value));
42
+ });
43
+ }
44
+ function p(e) {
45
+ const t = e.body;
46
+ if (t.type === "ObjectExpression")
47
+ return t;
48
+ if (t.type === "BlockStatement") {
49
+ const s = t.body;
50
+ for (const n of s)
51
+ if (n.type === "ReturnStatement") {
52
+ const r = n.argument;
53
+ if (r?.type === "ObjectExpression")
54
+ return r;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ function c(e, t) {
60
+ return e.type === "Identifier" && !t ? e.name || null : e.type === "Literal" && typeof e.value == "string" ? e.value : null;
61
+ }
62
+ export {
63
+ f as createSxVisitor,
64
+ c as getKeyName,
65
+ p as getReturnedObjectExpression,
66
+ u as visitSxValue
67
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clearstory/drywall-react",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "license": "UNLICENSED",
5
5
  "description": "a Clearstory software design system",
6
6
  "type": "module",