@botiverse/raft-ui 0.72.0 → 0.74.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/components/activity-inbox/activity-inbox-shell.d.mts +6 -3
  3. package/dist/components/activity-inbox/activity-inbox-shell.mjs +33 -14
  4. package/dist/components/activity-inbox/activity-inbox-shell.recipe.mjs +6 -1
  5. package/dist/components/announcement-dialog/announcement-dialog.d.mts +2 -1
  6. package/dist/components/announcement-dialog/announcement-dialog.mjs +5 -4
  7. package/dist/components/choice-list/choice-list.mjs +4 -1
  8. package/dist/components/choice-list/choice-list.recipe.mjs +2 -2
  9. package/dist/components/combobox-select/combobox-select.css +2 -0
  10. package/dist/components/composer/composer-suggestion-list.d.mts +5 -2
  11. package/dist/components/composer/composer-suggestion-list.mjs +10 -8
  12. package/dist/components/composer/composer.recipe.mjs +1 -1
  13. package/dist/components/dialog/dialog.d.mts +5 -3
  14. package/dist/components/dialog/dialog.mjs +16 -10
  15. package/dist/components/files/files.d.mts +5 -2
  16. package/dist/components/files/files.mjs +11 -2
  17. package/dist/components/files/files.recipe.mjs +1 -1
  18. package/dist/components/filter-chip/filter-chip.mjs +2 -1
  19. package/dist/components/markdown/markdown-parts.d.mts +1 -1
  20. package/dist/components/markdown/markdown-parts.mjs +1 -1
  21. package/dist/components/message-action-card/message-action-card-content.recipe.mjs +2 -1
  22. package/dist/components/message-action-card/message-action-card.d.mts +6 -4
  23. package/dist/components/message-action-card/message-action-card.mjs +14 -27
  24. package/dist/components/message-list/message-list.mjs +3 -3
  25. package/dist/components/mobile-nav/mobile-nav-server.d.mts +4 -2
  26. package/dist/components/mobile-nav/mobile-nav-server.mjs +9 -5
  27. package/dist/components/notification-center/notification-center.d.mts +5 -2
  28. package/dist/components/notification-center/notification-center.mjs +10 -8
  29. package/dist/components/panel/panel-action.mjs +1 -1
  30. package/dist/components/panel/panel.d.mts +10 -2
  31. package/dist/components/panel/panel.mjs +26 -3
  32. package/dist/components/panel/panel.recipe.mjs +2 -1
  33. package/dist/components/scroll-area/scroll-area.css +27 -10
  34. package/dist/components/scroll-area/types.d.mts +3 -1
  35. package/dist/components/scroll-area/use-scroll-area-container.d.mts +2 -1
  36. package/dist/components/search/search.d.mts +5 -3
  37. package/dist/components/search/search.mjs +9 -10
  38. package/dist/components/search/search.recipe.mjs +1 -1
  39. package/dist/components/segmented-control/segmented-control.mjs +1 -1
  40. package/dist/components/sidebar/sidebar.mjs +1 -1
  41. package/dist/components/tabs/tabs.mjs +1 -1
  42. package/dist/components/tasks/tasks-panel.d.mts +6 -2
  43. package/dist/components/tasks/tasks-panel.mjs +10 -7
  44. package/dist/components/tasks/tasks-panel.recipe.mjs +1 -1
  45. package/dist/components/toggle-group/toggle-group.mjs +1 -1
  46. package/dist/components/virtual-list/styles/virtual-list.css +3 -0
  47. package/dist/index.d.mts +1 -1
  48. package/dist/index.mjs +4 -4
  49. package/dist/lib/use-scrollbar-size.d.mts +3 -0
  50. package/dist/lib/use-scrollbar-size.mjs +3 -0
  51. package/dist/lint/prefer-scroll-area.d.mts +75 -0
  52. package/dist/lint/prefer-scroll-area.mjs +157 -0
  53. package/dist/lint/recommended.json +6 -0
  54. package/dist/lint.d.mts +22 -0
  55. package/dist/lint.mjs +29 -0
  56. package/dist/styles.css +3 -2
  57. package/package.json +6 -1
  58. package/skills/raft-ui-guide/SKILL.md +1 -0
  59. package/skills/raft-ui-guide/rules/technical-overview.md +1 -0
@@ -0,0 +1,75 @@
1
+ //#region src/lint/prefer-scroll-area.d.ts
2
+ /**
3
+ * raft-ui/prefer-scroll-area: every region that scrolls is a ScrollArea
4
+ * (scroll-area.md#native-scrolling), so a native overflow that makes an
5
+ * element scroll is reported where an app writes it.
6
+ *
7
+ * Reported:
8
+ * - a native-scrolling Tailwind class (`overflow`, `overflow-x` or `overflow-y`
9
+ * set to auto or scroll, with any variant prefix, `!`, or as an arbitrary
10
+ * property) in a string or template literal that is a class: the value of a
11
+ * `className`, `class` or `*ClassName` JSX attribute, or an argument of
12
+ * `cn`, `clsx`, `cx`, `tv`, `cva`, `twMerge`, `twJoin` or `classNames`,
13
+ * through conditionals, `&&`, arrays, objects (keys and values) and `+`;
14
+ * - an inline `style={{ overflow | overflowX | overflowY: "auto" | "scroll" }}`.
15
+ *
16
+ * Allowed with a `raft-ui: native-scroll <reason>` comment, line or block, on
17
+ * the line above the reported string (or at the end of its line): it covers
18
+ * the string that starts on that line only, not the rest of the call or the
19
+ * attribute it is in, so a class added to a multi-line `cn(...)` later is
20
+ * still reported. For an inline style, the line of its property. A comment
21
+ * without a reason doesn't count.
22
+ *
23
+ * It looks only at literals, with no scope or type information: a class built
24
+ * in a variable outside these calls isn't seen.
25
+ */
26
+ export interface Position {
27
+ line: number;
28
+ column: number;
29
+ }
30
+ export interface Node {
31
+ type: string;
32
+ parent?: Node | null;
33
+ loc?: {
34
+ start: Position;
35
+ end: Position;
36
+ };
37
+ range?: [number, number];
38
+ [key: string]: unknown;
39
+ }
40
+ export interface Comment {
41
+ type: string;
42
+ value: string;
43
+ loc?: {
44
+ start: Position;
45
+ end: Position;
46
+ };
47
+ range?: [number, number];
48
+ }
49
+ export interface SourceCode {
50
+ getAllComments(): Comment[];
51
+ getLocFromIndex(index: number): Position;
52
+ }
53
+ export interface RuleContext {
54
+ sourceCode?: SourceCode;
55
+ getSourceCode?: () => SourceCode;
56
+ report(descriptor: {
57
+ node: Node;
58
+ messageId: string;
59
+ }): void;
60
+ }
61
+ export type RuleVisitor = Record<string, (node: Node) => void>;
62
+ /** An ESLint rule, the format Oxlint's JS plugins take. */
63
+ export interface RuleModule {
64
+ meta: {
65
+ type: "problem" | "suggestion" | "layout";
66
+ docs: {
67
+ description: string;
68
+ url: string;
69
+ };
70
+ messages: Record<string, string>;
71
+ schema: readonly unknown[];
72
+ };
73
+ create(context: RuleContext): RuleVisitor;
74
+ }
75
+ //#endregion
@@ -0,0 +1,157 @@
1
+ //#region src/lint/prefer-scroll-area.ts
2
+ const NATIVE_SCROLL_CLASS = /(?<![\w-])overflow(?:-[xy])?-(?:auto|scroll)(?![\w-])/;
3
+ const NATIVE_SCROLL_ARBITRARY = /\[overflow(?:-[xy])?:[^\]]*\b(?:auto|scroll)\b[^\]]*\]/;
4
+ const NATIVE_SCROLL_STYLE_KEYS = /* @__PURE__ */ new Set([
5
+ "overflow",
6
+ "overflowX",
7
+ "overflowY"
8
+ ]);
9
+ const NATIVE_SCROLL_STYLE_VALUES = /* @__PURE__ */ new Set(["auto", "scroll"]);
10
+ const CLASS_FUNCTIONS = /* @__PURE__ */ new Set([
11
+ "cn",
12
+ "clsx",
13
+ "cx",
14
+ "tv",
15
+ "cva",
16
+ "twMerge",
17
+ "twJoin",
18
+ "classNames",
19
+ "classnames"
20
+ ]);
21
+ const CLASS_ATTRIBUTE = /^(?:class|className|\w+ClassName)$/;
22
+ const PASS_THROUGH = /* @__PURE__ */ new Set([
23
+ "ArrayExpression",
24
+ "BinaryExpression",
25
+ "ConditionalExpression",
26
+ "JSXExpressionContainer",
27
+ "LogicalExpression",
28
+ "ObjectExpression",
29
+ "ParenthesizedExpression",
30
+ "Property",
31
+ "SpreadElement",
32
+ "TemplateLiteral",
33
+ "TSAsExpression",
34
+ "TSNonNullExpression",
35
+ "TSSatisfiesExpression"
36
+ ]);
37
+ const ESCAPE = /\braft-ui:\s*native-scroll\s+\S/;
38
+ /** Whether a class string has a native-scrolling class in it. */
39
+ function hasNativeScrollClass(value) {
40
+ if (!value.includes("overflow")) return false;
41
+ return NATIVE_SCROLL_CLASS.test(value) || NATIVE_SCROLL_ARBITRARY.test(value);
42
+ }
43
+ function attributeName(node) {
44
+ const name = node.name;
45
+ return name?.type === "JSXIdentifier" ? name.name : void 0;
46
+ }
47
+ /** The attribute or call that makes a string a class, or undefined when it isn't one. */
48
+ function classContext(node) {
49
+ let child = node;
50
+ let parent = node.parent;
51
+ while (parent) {
52
+ if (parent.type === "JSXAttribute") {
53
+ const name = attributeName(parent);
54
+ return name !== void 0 && CLASS_ATTRIBUTE.test(name) ? parent : void 0;
55
+ }
56
+ if (parent.type === "CallExpression") {
57
+ const callee = parent.callee;
58
+ return callee.type === "Identifier" && CLASS_FUNCTIONS.has(callee.name) && parent.arguments.includes(child) ? parent : void 0;
59
+ }
60
+ if (!PASS_THROUGH.has(parent.type)) return void 0;
61
+ child = parent;
62
+ parent = parent.parent;
63
+ }
64
+ }
65
+ function stringValue(node) {
66
+ if (node.type === "Literal" && typeof node.value === "string") return node.value;
67
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
68
+ const [quasi] = node.quasis;
69
+ const value = quasi?.value;
70
+ return value?.cooked ?? value?.raw;
71
+ }
72
+ }
73
+ /** The string literals an inline style value can be: `"auto"`, `a ? "auto" : "hidden"`, `a && "auto"`. */
74
+ function styleValueStrings(node, out) {
75
+ if (node.type === "ConditionalExpression") {
76
+ styleValueStrings(node.consequent, out);
77
+ styleValueStrings(node.alternate, out);
78
+ } else if (node.type === "LogicalExpression") {
79
+ styleValueStrings(node.left, out);
80
+ styleValueStrings(node.right, out);
81
+ } else if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") styleValueStrings(node.expression, out);
82
+ else if (stringValue(node) !== void 0) out.push(node);
83
+ return out;
84
+ }
85
+ function propertyKey(property) {
86
+ const key = property.key;
87
+ if (property.computed) return stringValue(key);
88
+ return key.type === "Identifier" ? key.name : stringValue(key);
89
+ }
90
+ const preferScrollArea = {
91
+ meta: {
92
+ type: "suggestion",
93
+ docs: {
94
+ description: "Scrolling regions use ScrollArea from @botiverse/raft-ui, not a native overflow.",
95
+ url: "https://raft-design.botiverse.build/about/use-raft-ui/#lint-rules"
96
+ },
97
+ messages: { preferScrollArea: "Scrolling regions use ScrollArea from @botiverse/raft-ui (or PanelScrollViewport inside a Panel). If this one must scroll natively, say why in an `// raft-ui: native-scroll <reason>` comment on the line above. https://raft-design.botiverse.build/components/scroll-area/" },
98
+ schema: []
99
+ },
100
+ create(context) {
101
+ const sourceCode = context.sourceCode ?? context.getSourceCode?.();
102
+ let escapeLines;
103
+ function line(index) {
104
+ return sourceCode.getLocFromIndex(index).line;
105
+ }
106
+ /** The lines an escape comment covers: the one it ends on and the next. */
107
+ function escaped() {
108
+ if (escapeLines) return escapeLines;
109
+ escapeLines = /* @__PURE__ */ new Set();
110
+ for (const comment of sourceCode?.getAllComments() ?? []) {
111
+ if (!ESCAPE.test(comment.value)) continue;
112
+ const end = comment.loc?.end.line ?? line(comment.range[1]);
113
+ escapeLines.add(end);
114
+ escapeLines.add(end + 1);
115
+ }
116
+ return escapeLines;
117
+ }
118
+ function startLine(node) {
119
+ return node.loc?.start.line ?? line(node.range[0]);
120
+ }
121
+ /** Reports `node` unless an escape comment covers the line `anchor` starts on. */
122
+ function report(node, anchor) {
123
+ const lines = escaped();
124
+ if (lines.size > 0 && lines.has(startLine(anchor))) return;
125
+ context.report({
126
+ node,
127
+ messageId: "preferScrollArea"
128
+ });
129
+ }
130
+ function checkClassString(node, value) {
131
+ if (!hasNativeScrollClass(value)) return;
132
+ if (classContext(node)) report(node, node);
133
+ }
134
+ return {
135
+ Literal(node) {
136
+ if (typeof node.value === "string") checkClassString(node, node.value);
137
+ },
138
+ TemplateLiteral(node) {
139
+ checkClassString(node, node.quasis.map((quasi) => quasi.value.raw).join(" "));
140
+ },
141
+ JSXAttribute(node) {
142
+ if (attributeName(node) !== "style") return;
143
+ const value = node.value;
144
+ const expression = value?.type === "JSXExpressionContainer" ? value.expression : void 0;
145
+ if (expression?.type !== "ObjectExpression") return;
146
+ for (const property of expression.properties) {
147
+ if (property.type !== "Property") continue;
148
+ const key = propertyKey(property);
149
+ if (key === void 0 || !NATIVE_SCROLL_STYLE_KEYS.has(key)) continue;
150
+ for (const literal of styleValueStrings(property.value, [])) if (NATIVE_SCROLL_STYLE_VALUES.has(stringValue(literal).trim())) report(literal, property);
151
+ }
152
+ }
153
+ };
154
+ }
155
+ };
156
+ //#endregion
157
+ export { hasNativeScrollClass, preferScrollArea };
@@ -0,0 +1,6 @@
1
+ {
2
+ "jsPlugins": ["@botiverse/raft-ui/lint"],
3
+ "rules": {
4
+ "raft-ui/prefer-scroll-area": "error"
5
+ }
6
+ }
@@ -0,0 +1,22 @@
1
+ import { RuleContext, RuleModule, RuleVisitor } from "./lint/prefer-scroll-area.mjs";
2
+ //#region src/lint/index.d.ts
3
+ export interface RaftUiLintConfig {
4
+ name: string;
5
+ plugins: Record<string, RaftUiLintPlugin>;
6
+ rules: Record<string, "error" | "warn" | "off">;
7
+ }
8
+ export interface RaftUiLintPlugin {
9
+ meta: {
10
+ name: string;
11
+ };
12
+ rules: Record<string, RuleModule>;
13
+ configs: Record<string, RaftUiLintConfig>;
14
+ }
15
+ export declare const rules: Record<string, RuleModule>;
16
+ /** Every rule of the plugin with its severity, as `recommended.json` lists them. */
17
+ export declare const recommendedRules: Record<string, "error">;
18
+ declare const plugin: RaftUiLintPlugin;
19
+ /** ESLint flat config: `export default [raftUi.configs.recommended]`. */
20
+ export declare const recommended: RaftUiLintConfig;
21
+ //#endregion
22
+ export { type RuleContext, type RuleModule, type RuleVisitor, plugin as default };
package/dist/lint.mjs ADDED
@@ -0,0 +1,29 @@
1
+ import { preferScrollArea } from "./lint/prefer-scroll-area.mjs";
2
+ //#region src/lint/index.ts
3
+ /**
4
+ * raft-ui's lint plugin: the Raft Design System's rules for the code of an app
5
+ * built on raft-ui, in the ESLint plugin format, which Oxlint loads as a JS
6
+ * plugin (`jsPlugins`). Published as `@botiverse/raft-ui/lint`.
7
+ *
8
+ * Enable every rule at once by extending `recommended.json` from the same
9
+ * package (Oxlint) or adding `configs.recommended` (ESLint flat config), so a
10
+ * rule added in a later release reaches the app with the upgrade. See
11
+ * design/about/use-raft-ui.md#lint-rules.
12
+ */
13
+ const rules = { "prefer-scroll-area": preferScrollArea };
14
+ /** Every rule of the plugin with its severity, as `recommended.json` lists them. */
15
+ const recommendedRules = { "raft-ui/prefer-scroll-area": "error" };
16
+ const plugin = {
17
+ meta: { name: "raft-ui" },
18
+ rules,
19
+ configs: {}
20
+ };
21
+ /** ESLint flat config: `export default [raftUi.configs.recommended]`. */
22
+ const recommended = {
23
+ name: "raft-ui/recommended",
24
+ plugins: { "raft-ui": plugin },
25
+ rules: recommendedRules
26
+ };
27
+ plugin.configs.recommended = recommended;
28
+ //#endregion
29
+ export { plugin as default, recommended, recommendedRules, rules };
package/dist/styles.css CHANGED
@@ -893,8 +893,9 @@ textarea:-webkit-autofill:disabled {
893
893
  --list-cell: 1;
894
894
  }
895
895
 
896
- /* The scrollbar of a scroller that holds list cells (design/patterns/
897
- lists.md): an 8px classic scrollbar with a 4px thumb, so a cell's
896
+ /* For a native scroller of list cells that can't be a ScrollArea
897
+ (design/patterns/lists.md; raft-ui's own list scrollers are ScrollAreas,
898
+ whose overlay scrollbar takes no room): an 8px classic scrollbar with a 4px thumb, so a cell's
898
899
  end inset can come out of the scrollbar's own width and its fill sits 8px
899
900
  from both edges of the list. Styling ::-webkit-scrollbar makes Chromium
900
901
  draw this classic, always-visible 8px bar even where the platform's own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botiverse/raft-ui",
3
- "version": "0.72.0",
3
+ "version": "0.74.0",
4
4
  "description": "React components and CSS foundations for the Raft Design System.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -46,6 +46,11 @@
46
46
  "types": "./dist/wip.d.mts",
47
47
  "default": "./dist/wip.mjs"
48
48
  },
49
+ "./lint": {
50
+ "types": "./dist/lint.d.mts",
51
+ "default": "./dist/lint.mjs"
52
+ },
53
+ "./lint/recommended.json": "./dist/lint/recommended.json",
49
54
  "./fonts.css": "./dist/fonts.css",
50
55
  "./styles.css": "./dist/styles.css",
51
56
  "./package.json": "./package.json",
@@ -90,6 +90,7 @@ When reviewing or editing code that uses raft-ui, check:
90
90
  - [ ] Menu rows are `…Item` parts, not raw `<button>`s; raw buttons drop out of keyboard navigation
91
91
  - [ ] Product state (upload, permissions, read/unread) lives in the app; the component renders parts, not a `state` prop
92
92
  - [ ] No shadow written as `shadow-[var(--…)]`: named utility or fully literal
93
+ - [ ] Every region that scrolls is a `ScrollArea` (or `PanelScrollViewport` in a Panel), not `overflow-auto` / `overflow-scroll`; a native scroller kept on purpose has a `// raft-ui: native-scroll <reason>` comment. The `raft-ui/prefer-scroll-area` lint rule checks it: https://raft-design.botiverse.build/about/use-raft-ui.md#lint-rules
93
94
 
94
95
  ## Stability
95
96
 
@@ -19,6 +19,7 @@ description: "raft-ui's technical foundations, package contents, and integration
19
19
  | `@botiverse/raft-ui/foundation.css` | Native CSS theme variables |
20
20
  | `@botiverse/raft-ui/styles.css` | Tailwind integration |
21
21
  | `@botiverse/raft-ui/fonts.css` | Optional web fonts |
22
+ | `@botiverse/raft-ui/lint` | Lint plugin with the DS rules for apps |
22
23
 
23
24
  Component declarations live in `dist/index.d.mts` (see [API lookup](./lookup.md)). Version-matched Markdown guides and optional `raft-ui-guide` / `raft-ui-critique` skills ship in `skills/`.
24
25