@keyfunc/keybricks-ds 2.6.0 → 2.8.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 (178) hide show
  1. package/DEPLOY.md +3 -3
  2. package/DESIGN.md +21 -5
  3. package/INTEGRATION.md +35 -4
  4. package/_adherence.oxlintrc.json +56 -11
  5. package/_ds_bundle.js +2153 -216
  6. package/_scripts/build-index.mjs +1 -1
  7. package/_src/icon-index.js +1 -1
  8. package/{components/feedback → _vue-services}/MESSAGE_SERVICES.md +2 -2
  9. package/_vue-services/_internal/MessageBoxView.vue +150 -0
  10. package/{components/feedback → _vue-services}/_internal/service-utils.js +0 -12
  11. package/_vue-services/message.js +87 -0
  12. package/assets/icons/icon-index.json +1 -1
  13. package/changelog.js +21 -2
  14. package/components/_cards/accordion.card.html +1 -1
  15. package/components/_cards/action-chip.card.html +1 -1
  16. package/components/_cards/activity.card.html +1 -1
  17. package/components/_cards/alert.card.html +1 -1
  18. package/components/_cards/app-chrome.card.html +1 -1
  19. package/components/_cards/banner.card.html +1 -1
  20. package/components/_cards/breadcrumb.card.html +1 -1
  21. package/components/_cards/button-1-base.card.html +5 -2
  22. package/components/_cards/button-2-states.card.html +5 -5
  23. package/components/_cards/button-3-sizes-icons.card.html +2 -2
  24. package/components/_cards/button-4-group.card.html +2 -2
  25. package/components/_cards/button-5-ai.card.html +34 -0
  26. package/components/_cards/canvas-zoom-controls.card.html +1 -1
  27. package/components/_cards/cards-indicators.card.html +1 -1
  28. package/components/_cards/charts-1-sparkline.card.html +1 -1
  29. package/components/_cards/charts-10-gauge.card.html +1 -1
  30. package/components/_cards/charts-11-funnel.card.html +1 -1
  31. package/components/_cards/charts-12-treemap.card.html +1 -1
  32. package/components/_cards/charts-14-radar.card.html +1 -1
  33. package/components/_cards/charts-15-distribution.card.html +1 -1
  34. package/components/_cards/charts-16-pareto.card.html +1 -1
  35. package/components/_cards/charts-17-candlestick.card.html +1 -1
  36. package/components/_cards/charts-18-sankey.card.html +1 -1
  37. package/components/_cards/charts-19-chord.card.html +1 -1
  38. package/components/_cards/charts-2-line.card.html +1 -1
  39. package/components/_cards/charts-20-sunburst.card.html +1 -1
  40. package/components/_cards/charts-21-worldmap.card.html +1 -1
  41. package/components/_cards/charts-22-gantt.card.html +1 -1
  42. package/components/_cards/charts-23-bar3d.card.html +1 -1
  43. package/components/_cards/charts-24-pie3d.card.html +1 -1
  44. package/components/_cards/charts-25-surface3d.card.html +1 -1
  45. package/components/_cards/charts-26-scatter3d.card.html +1 -1
  46. package/components/_cards/charts-27-ribbon3d.card.html +1 -1
  47. package/components/_cards/charts-3-bar.card.html +1 -1
  48. package/components/_cards/charts-4-barlist.card.html +1 -1
  49. package/components/_cards/charts-5-donut.card.html +1 -1
  50. package/components/_cards/charts-6-scatter.card.html +1 -1
  51. package/components/_cards/charts-7-heatmap.card.html +1 -1
  52. package/components/_cards/charts-8-waterfall.card.html +1 -1
  53. package/components/_cards/charts-9-bullet.card.html +1 -1
  54. package/components/_cards/charts-99-fullpage.card.html +1 -1
  55. package/components/_cards/checkbox.card.html +1 -1
  56. package/components/_cards/codeblock.card.html +1 -1
  57. package/components/_cards/combobox.card.html +1 -1
  58. package/components/_cards/context-menu.card.html +1 -1
  59. package/components/_cards/date-picker.card.html +1 -1
  60. package/components/_cards/diffvalue.card.html +1 -1
  61. package/components/_cards/divider.card.html +1 -1
  62. package/components/_cards/drawer.card.html +1 -1
  63. package/components/_cards/dropdown-menu.card.html +1 -1
  64. package/components/_cards/dropzone.card.html +1 -1
  65. package/components/_cards/empty-state.card.html +1 -1
  66. package/components/_cards/filter-bar.card.html +1 -1
  67. package/components/_cards/icon-button.card.html +1 -1
  68. package/components/_cards/icons-0-usage.card.html +2 -2
  69. package/components/_cards/icons-1-core.card.html +1 -1
  70. package/components/_cards/icons-2-feather.card.html +1 -1
  71. package/components/_cards/icons-2b-lucide.card.html +1 -1
  72. package/components/_cards/icons-3-unicons.card.html +1 -1
  73. package/components/_cards/icons-4-datasource.card.html +1 -1
  74. package/components/_cards/input.card.html +1 -1
  75. package/components/_cards/kbd.card.html +5 -7
  76. package/components/_cards/keyvalue.card.html +1 -1
  77. package/components/_cards/listrow.card.html +1 -1
  78. package/components/_cards/loading.card.html +1 -1
  79. package/components/_cards/log-viewer.card.html +1 -1
  80. package/components/_cards/modal.card.html +8 -2
  81. package/components/_cards/multi-select.card.html +1 -1
  82. package/components/_cards/number-stepper.card.html +1 -1
  83. package/components/_cards/pagination.card.html +1 -1
  84. package/components/_cards/people.card.html +1 -1
  85. package/components/_cards/pipeline-node.card.html +1 -1
  86. package/components/_cards/pipeline-palette.card.html +1 -1
  87. package/components/_cards/radio.card.html +1 -1
  88. package/components/_cards/relative-time-picker.card.html +1 -1
  89. package/components/_cards/search-global.card.html +72 -0
  90. package/components/_cards/search.card.html +1 -1
  91. package/components/_cards/segmented-control.card.html +1 -1
  92. package/components/_cards/select.card.html +1 -1
  93. package/components/_cards/slider.card.html +1 -1
  94. package/components/_cards/smart-table.card.html +1 -1
  95. package/components/_cards/spreadsheet.card.html +1 -1
  96. package/components/_cards/stepper.card.html +1 -1
  97. package/components/_cards/table.card.html +10 -1
  98. package/components/_cards/tabs.card.html +1 -1
  99. package/components/_cards/tag.card.html +1 -1
  100. package/components/_cards/textarea.card.html +1 -1
  101. package/components/_cards/timeline.card.html +1 -1
  102. package/components/_cards/toast.card.html +11 -1
  103. package/components/_cards/toggles.card.html +1 -1
  104. package/components/_cards/tooltip.card.html +1 -1
  105. package/components/_cards/tree.card.html +22 -1
  106. package/components/_vue/index.mjs +2 -0
  107. package/components/buttons/Button.d.ts +14 -5
  108. package/components/buttons/Button.jsx +45 -8
  109. package/components/buttons/Button.vue +40 -38
  110. package/components/buttons/Button.vue.d.ts +4 -1
  111. package/components/buttons/ai-button.js +54 -0
  112. package/components/buttons/buttons.prompt.md +7 -2
  113. package/components/data/Table.d.ts +5 -0
  114. package/components/data/Table.jsx +10 -5
  115. package/components/data/Table.vue +9 -3
  116. package/components/data/data.prompt.md +2 -0
  117. package/components/data/row-open.js +13 -0
  118. package/components/display/Kbd.d.ts +3 -1
  119. package/components/display/Kbd.jsx +8 -3
  120. package/components/display/Kbd.vue +7 -3
  121. package/components/display/display.prompt.md +3 -1
  122. package/components/feedback/Modal.d.ts +65 -7
  123. package/components/feedback/Modal.jsx +52 -22
  124. package/components/feedback/Modal.vue +24 -6
  125. package/components/feedback/Toast.vue +7 -7
  126. package/components/feedback/ToastStack.vue +37 -4
  127. package/components/feedback/feedback.prompt.md +6 -0
  128. package/components/feedback/modal-behavior.js +50 -0
  129. package/components/forms/GlobalSearch.d.ts +83 -0
  130. package/components/forms/GlobalSearch.jsx +172 -0
  131. package/components/forms/GlobalSearch.vue +161 -0
  132. package/components/forms/forms.prompt.md +7 -0
  133. package/components/forms/global-search.js +161 -0
  134. package/components/icons/Icon.d.ts +2 -0
  135. package/components/icons/Icon.jsx +14 -6
  136. package/components/icons/Icon.vue +11 -4
  137. package/components/icons/icon-paths.js +4 -2
  138. package/components/index.d.ts +1 -0
  139. package/components/index.js +1 -0
  140. package/components/navigation/TopBar.d.ts +9 -3
  141. package/components/navigation/TopBar.jsx +5 -7
  142. package/components/navigation/TopBar.vue +5 -6
  143. package/components/navigation/Tree.d.ts +5 -1
  144. package/components/navigation/Tree.jsx +9 -7
  145. package/components/navigation/Tree.vue +7 -6
  146. package/components/navigation/navigation.prompt.md +2 -0
  147. package/covers.js +4 -2
  148. package/gallery.html +1 -1
  149. package/guidelines/brand-logo.html +1 -1
  150. package/guidelines/colors-schemes.html +1 -1
  151. package/guidelines/colors-transparency.html +1 -1
  152. package/guidelines/foundations-borders.html +1 -1
  153. package/guidelines/foundations-chrome.html +1 -1
  154. package/guidelines/foundations-motion.html +1 -1
  155. package/guidelines/foundations-size-tiers.html +1 -1
  156. package/guidelines/foundations-spacing.html +1 -1
  157. package/guidelines/interaction-states.html +1 -1
  158. package/guidelines/type-extended.html +1 -1
  159. package/guidelines/type-mono.html +1 -1
  160. package/guidelines/type-scale.html +1 -1
  161. package/guidelines/type-weights.html +1 -1
  162. package/index.html +10 -10
  163. package/package.json +7 -6
  164. package/readme.md +4 -4
  165. package/readme.zh-CN.md +4 -4
  166. package/site-nav.js +1 -1
  167. package/styles.css +1 -0
  168. package/tokens/ai.css +17 -0
  169. package/tokens/colors.css +1 -1
  170. package/tokens/tokens.d.ts +28 -0
  171. package/tokens/tokens.js +47 -5
  172. package/tokens/tokens.json +123 -5
  173. package/components/feedback/_internal/MessageBoxView.vue +0 -237
  174. package/components/feedback/_internal/MessageView.vue +0 -73
  175. package/components/feedback/message.js +0 -68
  176. /package/{components/feedback → _vue-services}/message-box.d.ts +0 -0
  177. /package/{components/feedback → _vue-services}/message-box.js +0 -0
  178. /package/{components/feedback → _vue-services}/message.d.ts +0 -0
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x340" name="People" subtitle="Avatar · AvatarGroup — initials, sizes, real images" -->
1
+ <!-- @dsCard group="Components" viewport="700x326" name="People" subtitle="Avatar · AvatarGroup — initials, sizes, real images" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x1800" name="Pipeline Node Card" subtitle="220×90 node — category hues · status badges · ports with hover, drag-to-connect and drop states" -->
1
+ <!-- @dsCard group="Components" viewport="700x1781" name="Pipeline Node Card" subtitle="220×90 node — category hues · status badges · ports with hover, drag-to-connect and drop states" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x920" name="Pipeline Palette" subtitle="240px component drawer — grid/list toggle · collapsible hued categories" -->
1
+ <!-- @dsCard group="Components" viewport="700x771" name="Pipeline Palette" subtitle="240px component drawer — grid/list toggle · collapsible hued categories" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x1750" name="Radio Button" subtitle="Single choice from a small set — md/sm · description · disabled · row · card variant · arrow-key navigation" -->
1
+ <!-- @dsCard group="Components" viewport="700x1741" name="Radio Button" subtitle="Single choice from a small set — md/sm · description · disabled · row · card variant · arrow-key navigation" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x880" name="Relative Time Picker" subtitle="Filter by how long ago · md / sm / lg · unlimited · disabled · localised labels" -->
1
+ <!-- @dsCard group="Components" viewport="700x871" name="Relative Time Picker" subtitle="Filter by how long ago · md / sm / lg · unlimited · disabled · localised labels" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -0,0 +1,72 @@
1
+ <!-- @dsCard group="Components" viewport="1000x3634" name="Search · Global" subtitle="GlobalSearch — TopBar search with ⌘K / Ctrl K · scopes · grouped results · Prophet AI hand-off · Deep Search" -->
2
+ <!DOCTYPE html>
3
+ <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
+ <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
5
+ <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
6
+ <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
7
+ <script src="../../_ds_bundle.js"></script>
8
+ <script src="../_shared/view-source.js"></script>
9
+ <script src="../_shared/card-kit.js"></script>
10
+ <style>.gs-frame{border:1px solid var(--border-card);border-radius:var(--radius-lg);overflow:hidden;background:var(--bg-app)}.gs-note{font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary);padding:10px 24px}</style></head>
11
+ <body><div id="root"></div>
12
+ <script type="text/babel">
13
+ const { GlobalSearch, TopBar, GLOBAL_SEARCH_LABELS } = window.KeybricksDesignSystem_71231a;
14
+ const { KBSec: Sec } = window;
15
+ const ITEMS = [
16
+ { kind: "feature", title: "Revenue Planning", sub: "Planning · Forecast models", keywords: ["forecast", "budget"] },
17
+ { kind: "feature", title: "Revenue Recognition Rules", sub: "Rules · IFRS 15" },
18
+ { kind: "feature", title: "Consolidation Monitor", sub: "Close · Group entities", keywords: ["revenue", "close"] },
19
+ { kind: "feature", title: "FX Rates", sub: "Data · Currency tables", keywords: ["currency", "revenue"] },
20
+ { kind: "feature", title: "Revenue Allocation", sub: "Rules · Cost centers" },
21
+ { kind: "document", title: "Q3 Revenue Bridge", sub: "Finance / Reports · updated 2h ago", meta: "Report", tone: "join" },
22
+ { kind: "document", title: "Revenue by Entity FY24", sub: "Finance / Sheets · updated yesterday", meta: "Sheet", tone: "source" },
23
+ { kind: "document", title: "Revenue Forecast Model", sub: "Planning / Models · updated 3d ago", meta: "Model", tone: "transform", icon: "box" },
24
+ { kind: "document", title: "APAC Revenue Review", sub: "Finance / Reports · updated last week", meta: "Report", tone: "join" },
25
+ { kind: "document", title: "Revenue Close Checklist", sub: "Close / Docs · updated last week", meta: "Doc", tone: "output" },
26
+ { kind: "document", title: "Deferred Revenue Schedule", sub: "Finance / Sheets · updated 2 weeks ago", meta: "Sheet", tone: "source" },
27
+ { kind: "member", title: "Revenue (4000)", sub: "Account · Income statement" },
28
+ { kind: "member", title: "Other Revenue (4900)", sub: "Account · Income statement" },
29
+ { kind: "member", title: "Revenue Adjustments", sub: "Scenario · Actuals" },
30
+ { kind: "member", title: "Net Revenue", sub: "Account · Calculated" },
31
+ { kind: "action", title: "Run revenue consolidation", sub: "Close · about 4 min", icon: "play" },
32
+ { kind: "action", title: "Create revenue forecast", sub: "Planning", icon: "plus" },
33
+ ];
34
+ const RECENT = [ITEMS[5], ITEMS[6], ITEMS[7]];
35
+ const PROMPTS = ["Which entity had the largest YoY revenue change in Q3?", "Explain how the FX model translates currencies", "Which workflows are waiting for my approval?"];
36
+ const STEPS = ["Understand the question and plan the search", "Search document titles and text", "Read model and dimension data", "Cross-check sources", "Write conclusion with citations"];
37
+ const DONE = { status: "done", elapsed: "42 s", steps: STEPS.map((label, i) => ({ label, state: "done", meta: ["2 s", "11 s", "14 s", "8 s", "7 s"][i] })), conclusion: ["APAC drove most of the Q3 revenue change: +18% YoY, led by Shanghai Ops after the July price update.", "EMEA was flat (+1%); AMER fell 4% on the delayed Northwind renewal."], sources: ["Q3 Revenue Bridge", "Revenue by Entity FY24", "APAC Revenue Review", "FX Rates", "Revenue (4000)"] };
38
+ const slow = (term) => new Promise((r) => setTimeout(() => r(ITEMS.filter((it) => (it.title + " " + (it.keywords || []).join(" ")).toLowerCase().includes(term.toLowerCase()))), 900));
39
+ const never = () => new Promise(() => {});
40
+ function deepSearch(term, update) {
41
+ let i = 0; const steps = STEPS.map((label) => ({ label, state: "pending" }));
42
+ const tick = () => { steps.forEach((s, j) => { s.state = j < i ? "done" : j === i ? "running" : "pending"; }); update({ status: "running", steps: steps.map((s) => ({ ...s })) }); if (i++ < STEPS.length) t = setTimeout(tick, 1100); else update({ ...DONE }); };
43
+ let t = setTimeout(tick, 200); return () => clearTimeout(t);
44
+ }
45
+ const base = { items: ITEMS, recentSearches: ["revenue bridge", "FX rates", "approval workflows"], recent: RECENT, prompts: PROMPTS };
46
+ function Sheet(){
47
+ const [asked, setAsked] = React.useState(null);
48
+ return <div className="kb-sheet">
49
+ <Sec title="Live — in the TopBar" note="Click the field or press ⌘K / Ctrl K. Type “revenue”: a 300ms pause, then a spinner and skeleton rows, then grouped results. ↑↓ select, ↵ open, ⌘↵ / Ctrl Enter asks Prophet AI, Esc closes. Choose Deep Search on a query to watch it run.">
50
+ <div className="gs-frame"><TopBar user={{ name: "Felix Wang", email: "felix@keyfunc.com" }} search={{ ...base, onQuery: slow, onDeepSearch: deepSearch }} onAi={(q) => setAsked(q)} />
51
+ <div className="gs-note">{asked == null ? "Prophet AI hand-off: nothing asked yet." : "Prophet AI panel would open with: " + (asked ? "“" + asked + "”" : "(no question)")}</div></div>
52
+ </Sec>
53
+ <Sec title="Empty — recent searches, recent and Try Asking Prophet AI" note="No scope bar and no pinned rows. Prompts are rows: ↑↓ reaches them and ↵ hands the prompt to Prophet AI. The selected prompt's tile takes the AI hover treatment.">
54
+ <GlobalSearch inline {...base} />
55
+ </Sec>
56
+ <Sec title="Results — scope bar, grouped results, pinned AI rows" note="Under All, groups show Features 4 · Documents 5 · Dimension Members 3 · Actions 2, with View all to switch scope. The matched text is highlighted. Ask Prophet AI and Deep Search stay pinned however many results there are.">
57
+ <GlobalSearch inline {...base} defaultQuery="revenue" onDeepSearch={deepSearch} style={{ maxHeight: 620 }} />
58
+ </Sec>
59
+ <Sec title="Loading" note="After a 300ms typing pause: spinner in place of the search icon, “Searching…”, skeleton rows. The pinned rows already follow the live text.">
60
+ <GlobalSearch inline {...base} defaultQuery="forecast" onQuery={never} onDeepSearch={deepSearch} />
61
+ </Sec>
62
+ <Sec title="Deep Search — done" note="Steps fold into one row (click to expand), then the cited conclusion. Ask Follow-up hands off to Prophet AI; Esc goes back to results.">
63
+ <GlobalSearch inline {...base} defaultQuery="Q3 revenue change by entity" defaultDeep={DONE} onDeepSearch={deepSearch} />
64
+ </Sec>
65
+ <Sec title="中文 — labels={GLOBAL_SEARCH_LABELS.zh}" note="Prophet AI is 方知 in Chinese, with no “AI” suffix. The other strings are drafts.">
66
+ <div lang="zh-CN"><GlobalSearch inline labels={GLOBAL_SEARCH_LABELS.zh} items={ITEMS} defaultQuery="revenue" onDeepSearch={deepSearch} style={{ maxHeight: 560 }} /></div>
67
+ </Sec>
68
+ </div>;
69
+ }
70
+ ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
71
+ if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"GlobalSearch.jsx","path":"../forms/GlobalSearch.jsx"},{"name":"global-search.js","path":"../forms/global-search.js"}]); }, 0);
72
+ </script></body></html>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x770" name="Search Input" subtitle="Bordered · bare top-bar · with value/clear · disabled" -->
1
+ <!-- @dsCard group="Components" viewport="700x756" name="Search Input" subtitle="Bordered · bare top-bar · with value/clear · disabled" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x1880" name="Segmented Control" subtitle="Mutually exclusive views in one inset track — md/sm · counts · icons · icon-only · disabled option · full width · ViewToggle" -->
1
+ <!-- @dsCard group="Components" viewport="700x1864" name="Segmented Control" subtitle="Mutually exclusive views in one inset track — md/sm · counts · icons · icon-only · disabled option · full width · ViewToggle" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x550" name="Select" subtitle="md / sm · placeholder · disabled · error" -->
1
+ <!-- @dsCard group="Components" viewport="700x513" name="Select" subtitle="md / sm · placeholder · disabled · error" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x300" name="Slider" subtitle="Filled track · ring knob · value bubble · min/max labels" -->
1
+ <!-- @dsCard group="Components" viewport="700x267" name="Slider" subtitle="Filled track · ring knob · value bubble · min/max labels" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x1470" name="Smart Table (Database Grid)" subtitle="Frozen header and row numbers · default and compact density · typed fields · hue pills · aggregates · + New" -->
1
+ <!-- @dsCard group="Components" viewport="700x1454" name="Smart Table (Database Grid)" subtitle="Frozen header and row numbers · default and compact density · typed fields · hue pills · aggregates · + New" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="924x600" name="Spreadsheet" subtitle="Formula bar · frozen headers · range selection · =SUM() · sheet tabs · status bar" -->
1
+ <!-- @dsCard group="Components" viewport="924x590" name="Spreadsheet" subtitle="Formula bar · frozen headers · range selection · =SUM() · sheet tabs · status bar" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x630" name="Stepper" subtitle="Multi-step flow header — done · current · upcoming" -->
1
+ <!-- @dsCard group="Components" viewport="700x618" name="Stepper" subtitle="Multi-step flow header — done · current · upcoming" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x11200" name="Table" subtitle="Anatomy · densities · cell types (display · editable · per tier) · two-line name · sorting · selection · frames · column resize · states · composition" -->
1
+ <!-- @dsCard group="Components" viewport="700x11729" name="Table" subtitle="Anatomy · densities · cell types (display · editable · per tier) · two-line name · whole-row open · sorting · selection · frames · column resize · states · composition" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -167,6 +167,11 @@ function Sheet() {
167
167
  const [srt, setSrt] = React.useState(null);
168
168
  const [exp, setExp] = React.useState(["budget"]);
169
169
  const [opened, setOpened] = React.useState(null);
170
+ const [rowExp, setRowExp] = React.useState(["budget"]);
171
+ const [rowSel, setRowSel] = React.useState([]);
172
+ const [rowOpened, setRowOpened] = React.useState(null);
173
+ const rowToggle = (w, v, next) => setRowSel(w === "all" ? (next || []) : v ? [...rowSel, w] : rowSel.filter(i => i !== w));
174
+ const openWholeRow = (r) => { if (r.children) setRowExp(rowExp.includes(r.id) ? rowExp.filter(k => k !== r.id) : [...rowExp, r.id]); else setRowOpened(r.name); };
170
175
  const [pin, setPin] = React.useState([3]);
171
176
  const [lock, setLock] = React.useState([0, 3]);
172
177
  const lockFn = lockRule(lock);
@@ -225,6 +230,10 @@ function Sheet() {
225
230
  <Table bordered tree columns={DOCS} rows={TREE_ROWS} expandedKeys={exp} onExpandChange={(r, open, keys) => setExp(keys)} onRowOpen={(r) => setOpened(r.name)} />
226
231
  <div className="note">Each level indents 24px; the 20px arrow slot plus 4px puts a child's arrow under its parent's tile. Rows without children keep the slot, so tiles line up. Click a tile or a title to open the row (hand cursor; Enter or Space on the title). Sorting reorders siblings only. Opened: <code>{opened || "none"}</code> · <code>expandedKeys</code>: <code>{JSON.stringify(exp)}</code></div>
227
232
  </Case>
233
+ <Case k={'rowOpen="row" — click anywhere on a row to open it · whole row shows the hand cursor'}>
234
+ <Table bordered tree selectable rowOpen="row" columns={DOCS} rows={TREE_ROWS} selected={rowSel} onToggle={rowToggle} expandedKeys={rowExp} onExpandChange={(r, open, keys) => setRowExp(keys)} onRowOpen={openWholeRow} onRowMore={(r) => setRowOpened("⋯ " + r.name)} />
235
+ <div className="note">Default <code>rowOpen="title"</code> opens on the tile and the title only (above). With <code>"row"</code> any blank part of a body row calls <code>onRowOpen(row, index, event)</code>. Clicks on the checkbox, the ⋯ menu, the tree arrow, buttons, inputs and links do their own job and never open the row; neither does a click that ends a text selection, nor the header or a resize handle. The tile and the title open the row once, not twice. Here a folder row expands or collapses (the app decides in <code>onRowOpen</code>), a document opens. Last: <code>{rowOpened || "none"}</code></div>
236
+ </Case>
228
237
  <Case k="sm · 50px rows — 28px tile, 8px to the text, 12px title, 8px padding"><Table size="sm" bordered columns={DOCS} rows={DOC_ROWS.slice(2, 4)} /></Case>
229
238
  <Case k="lg · 68px rows — 40px tile r10, 20px icon, 4px between lines, 14px padding"><Table size="lg" bordered columns={DOCS} rows={DOC_ROWS.slice(2, 4)} /></Case>
230
239
  <Case k="md · CJK — description 13/18, row 60px (lang=&quot;zh-CN&quot;) · labels: the sort and ⋯ tooltips in Chinese"><div lang="zh-CN"><Table bordered labels={ZH_LABELS} columns={[{ ...DOCS[0], label: "名称" }, { ...DOCS[1], label: "详情" }, { ...DOCS[2], label: "修改时间" }]} rows={DOC_ROWS_ZH} /></div></Case>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x1130" name="Tabs" subtitle="Underline tabs · panel tabs · pill filter tabs · counts on every kind" -->
1
+ <!-- @dsCard group="Components" viewport="700x1118" name="Tabs" subtitle="Underline tabs · panel tabs · pill filter tabs · counts on every kind" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x450" name="Tag" subtitle="Removable filter tags — distinct from Badge/ActionChip" -->
1
+ <!-- @dsCard group="Components" viewport="700x438" name="Tag" subtitle="Removable filter tags — distinct from Badge/ActionChip" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="924x680" name="Textarea" subtitle="Default · character count · over the limit · disabled" -->
1
+ <!-- @dsCard group="Components" viewport="924x674" name="Textarea" subtitle="Default · character count · over the limit · disabled" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x390" name="Timeline" subtitle="Vertical activity timeline — toned icon nodes on a hairline spine" -->
1
+ <!-- @dsCard group="Components" viewport="700x373" name="Timeline" subtitle="Vertical activity timeline — toned icon nodes on a hairline spine" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x800" name="Toast" subtitle="Tones · with/without meta · live ToastStack demo" -->
1
+ <!-- @dsCard group="Components" viewport="700x1087" name="Toast" subtitle="Placement · auto-dismiss · repeat count · Tones · with/without meta · live ToastStack demo" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -14,6 +14,9 @@ const { Toast, ToastStack, Button } = window.KeybricksDesignSystem_71231a;
14
14
  const { KBLive: Live } = window;
15
15
  function Sheet(){
16
16
  const [toasts,setToasts]=React.useState([]);
17
+ const [place,setPlace]=React.useState("top-center");
18
+ const [stack,setStack]=React.useState([]);
19
+ const push=(tone,title)=>setStack(s=>{const ex=s.find(t=>t.title===title);if(ex)return s.map(t=>t===ex?{...t,count:(t.count||1)+1}:t);return [...s,{id:Date.now()+Math.random(),tone,title}];});
17
20
  const notify=(tone,title,meta)=>{const id=Date.now()+Math.random();setToasts(s=>[...s,{id,tone,title,meta}]);setTimeout(()=>setToasts(s=>s.filter(t=>t.id!==id)),3200);};
18
21
  return <div>
19
22
  <div className="col">
@@ -28,6 +31,13 @@ function Sheet(){
28
31
  <Button variant="outline" onClick={()=>notify("danger","Task failed","Retry available")}>Trigger danger</Button>
29
32
  </div>
30
33
  <ToastStack toasts={toasts} onDismiss={(id)=>setToasts(s=>s.filter(t=>t.id!==id))}/>
34
+ <div style={{borderTop:"1px solid var(--border-subtle)",marginTop:20,paddingTop:16,display:"flex",flexDirection:"column",gap:10}}>
35
+ <span style={{fontSize:"var(--text-xs)",fontWeight:600,color:"var(--text-muted)"}}>ToastStack — placement · duration · repeat count</span>
36
+ <div style={{display:"flex",flexWrap:"wrap",gap:8}}>{["top-center","top-right","bottom-right","bottom-center"].map(p=><Button key={p} variant={p===place?"primary-soft":"outline"} onClick={()=>setPlace(p)}>{p}</Button>)}</div>
37
+ <div style={{display:"flex",flexWrap:"wrap",gap:8}}><Button onClick={()=>push("success","Saved")}>Saved</Button><Button variant="outline" onClick={()=>push("warning","Check the highlighted fields")}>Warning</Button><Button variant="outline" onClick={()=>push("danger","Sync failed")}>Error</Button></div>
38
+ <span style={{fontSize:"var(--text-xs)",lineHeight:"17px",color:"var(--text-secondary)",maxWidth:"64ch"}}><code>placement</code> sets the corner or edge (default <code>bottom-right</code>; the Vue Message service uses <code>top-center</code>). <code>duration={"{3000}"}</code> dismisses each toast after 3 s, paused while the pointer or focus is on it. Clicking the same message again shows <code>× 2</code> instead of stacking a copy, and restarts its timer. Toasts announce themselves to screen readers (danger and warning as alerts).</span>
39
+ </div>
40
+ <ToastStack toasts={stack} placement={place} duration={3000} onDismiss={(id)=>setStack(s=>s.filter(t=>t.id!==id))}/>
31
41
  </div>;
32
42
  }
33
43
  ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x2780" name="Toggle Switch" subtitle="Binary settings pill — sizes · semantic red→green · glyph knobs · inline text · captions · async loading" -->
1
+ <!-- @dsCard group="Components" viewport="700x2771" name="Toggle Switch" subtitle="Binary settings pill — sizes · semantic red→green · glyph knobs · inline text · captions · async loading" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x510" name="Tooltip" subtitle="Placements — top · bottom · left · right, on icon and text triggers" -->
1
+ <!-- @dsCard group="Components" viewport="700x499" name="Tooltip" subtitle="Placements — top · bottom · left · right, on icon and text triggers" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x3170" name="Tree" subtitle="Cascading checkboxes · disabled nodes · lines · async load · search · drag · reorder · keyboard" -->
1
+ <!-- @dsCard group="Components" viewport="700x3385" name="Tree" subtitle="Size tiers · rows without an icon · Cascading checkboxes · disabled nodes · lines · async load · search · drag · reorder · keyboard" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -178,7 +178,28 @@ function WithActions(){
178
178
  </div>;
179
179
  }
180
180
 
181
+ const ROOTED = [{key:"docs",title:"Documents",icon:false,children:FILES}];
182
+
183
+ function Tiers(){
184
+ return <div>
185
+ <span className="lbl">Size tiers — follows the region's data-size</span>
186
+ <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:16}}>
187
+ {["sm","md"].map(t=><div key={t} data-size={t}><div className="hint" style={{paddingBottom:4}}>{t==="sm"?"sm · 28px rows · 12px text (13px CJK)":"md · 32px rows · 14px text"}</div><Tree items={FILES} defaultExpanded={["finance"]} defaultActive="close"/></div>)}
188
+ </div>
189
+ <div className="hint">Row height <code>--control-h</code>, text <code>--control-text</code> / <code>--control-leading</code> / <code>--control-tracking</code>, glyph <code>--control-icon</code> − 1px. md is unchanged; lg rows are 40px.</div>
190
+ </div>;
191
+ }
192
+
193
+ function NoIconRoot(){
194
+ return <div>
195
+ <span className="lbl">icon: false — a root without a glyph</span>
196
+ <Tree items={ROOTED} defaultExpanded={["docs","finance"]} defaultActive="docs"/>
197
+ <div className="hint">The node draws no glyph and keeps no gap for one: its title follows the arrow. Works with <code>showIcon</code> on; every other row keeps its glyph.</div>
198
+ </div>;
199
+ }
200
+
181
201
  function Sheet(){return <div className="g">
202
+ <Tiers/><NoIconRoot/>
182
203
  <Basic/><Checkable/>
183
204
  <Strict/><DisabledCase/>
184
205
  <Lines/><Multi/>
@@ -94,6 +94,7 @@ export { default as Combobox } from "../forms/Combobox.vue";
94
94
  export { default as DatePicker } from "../forms/DatePicker.vue";
95
95
  export { default as Dropzone } from "../forms/Dropzone.vue";
96
96
  export { default as Field } from "../forms/Field.vue";
97
+ export { default as GlobalSearch } from "../forms/GlobalSearch.vue";
97
98
  export { default as Input } from "../forms/Input.vue";
98
99
  export { default as MultiSelect } from "../forms/MultiSelect.vue";
99
100
  export { default as NumberStepper } from "../forms/NumberStepper.vue";
@@ -150,3 +151,4 @@ export { PIPELINE_CATALOG } from "../pipeline/pipeline-catalog.js";
150
151
  export { VUE_ICON_PATHS } from "../icons/icon-paths.js";
151
152
  export { FEATHER_PATHS } from "../icons/feather-paths.js";
152
153
  export { LUCIDE_VERSION, loadLucide, setLucideSource } from "../icons/lucide-loader.js";
154
+ export { GLOBAL_SEARCH_LABELS, GS_ICONS, GS_KINDS } from "../forms/global-search.js";
@@ -3,14 +3,16 @@ import * as React from "react";
3
3
  * Keybricks button — flat, hairline. Height, text, icon and padding come from the size tier of the region it sits in.
4
4
  */
5
5
  export interface ButtonProps {
6
- /** primary (blue #155dfc) | dark (#171717, e.g. "AI Assistant") | outline | ghost | primary-soft (blue tint toggle, e.g. "Data Preview") | danger (solid red, destructive) | danger-soft (red tint, e.g. "Remove") | text (no background/border ever — underlines on hover, e.g. "View all", "Cancel") */
7
- variant?: "primary" | "dark" | "outline" | "ghost" | "primary-soft" | "danger" | "danger-soft" | "text";
6
+ /** primary (blue #155dfc) | dark (#171717, e.g. "Publish" — never for AI; use ai) | outline | ghost | primary-soft (blue tint toggle, e.g. "Data Preview") | danger (solid red, destructive) | danger-soft (red tint, e.g. "Remove") | text (no background/border ever — underlines on hover, e.g. "View all", "Cancel") | ai (Prophet AI / 方知 entry points ONLY — animated 1px gradient ring, gradient tint + glow on hover; icon defaults to "prophet-sparkle"; at most one per region) */
7
+ variant?: "primary" | "dark" | "outline" | "ghost" | "primary-soft" | "danger" | "danger-soft" | "text" | "ai";
8
8
  /** Size tier: sm 28px/12px text (13px in CJK) · md 32px/14px · lg 40px/14px. Omit to inherit the tier of the enclosing region
9
9
  * (toolbar, form, table, dialog — or the app root), which is the default and the normal case: controls that share
10
10
  * a row must share a tier. Set it only on a control that stands alone. */
11
11
  size?: "sm" | "md" | "lg";
12
- /** Icon glyph name */
13
- icon?: string;
12
+ /** Icon glyph name, or your own element (Vue: name, component or VNode; #icon slot). variant="ai" takes a glyph name only. */
13
+ icon?: string | React.ReactNode;
14
+ /** Separate trailing mark (e.g. "chevron-down" on a menu button); stays visible while loading. Vue: also #trailing slot. */
15
+ trailingIcon?: string | React.ReactNode;
14
16
  /** Overrides the tier's icon size (14 at sm, 16 at md/lg) */
15
17
  iconSize?: number;
16
18
  /** Where `icon` renders relative to the label (default "left") */
@@ -26,11 +28,18 @@ export interface ButtonProps {
26
28
  iconOnly?: boolean;
27
29
  /** aria-label, required when iconOnly */
28
30
  label?: string;
31
+ /** Fallback label when there are no children */
32
+ text?: string;
33
+ /** Native button type (default "button"); "submit" for legacy forms */
34
+ nativeType?: "button" | "submit" | "reset";
35
+ /** Force the hover styling — for specimens */
36
+ hovered?: boolean;
29
37
  /** Force the press (mouse-down) styling — for specimens and controlled toggles */
30
38
  pressed?: boolean;
31
39
  /** Force the keyboard-focus ring — for specimens */
32
40
  focused?: boolean;
33
- onClick?: () => void;
41
+ /** Receives the native event; not called while disabled or loading */
42
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
34
43
  children?: React.ReactNode;
35
44
  style?: React.CSSProperties;
36
45
  }
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { Icon } from "../icons/Icon.jsx";
3
+ import { aiStyles, aiSpinDelay, rampAiSpin } from "./ai-button.js";
3
4
 
4
5
  /* Every dimension comes from the size tier the button sits in (tokens/layout.css): height, text, icon and padding
5
6
  switch together when a region declares data-size="sm" | "md" | "lg". A `size` prop only stamps that attribute on
@@ -35,14 +36,50 @@ function Spinner({ size, color }) {
35
36
  return <span style={{ display: "inline-block", width: size, height: size, ...(size ? null : tierIcon), borderRadius: "var(--radius-full)", border: "2px solid currentColor", borderTopColor: "transparent", opacity: .9, color, animation: "kb-spin .7s linear infinite", flexShrink: 0 }} />;
36
37
  }
37
38
 
38
- /** Keybricks button. variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text; size: sm 28 | md 32 | lg 40 — omit it to inherit the region's tier (the default). Interaction states: hover (one step darker) · press (two steps, set via mouse-down) · keyboard focus ring · loading · disabled. */
39
- export function Button({ variant = "primary", size, icon, iconSize, iconPosition = "left", medium, disabled, loading, fullWidth, iconOnly, label, pressed, focused, children, style, onClick, ...rest }) {
39
+ /** variant="ai" — Prophet AI (方知) entry points only. Same box as any Button at the tier; see ai-button.js. */
40
+ function AiButton({ size, icon, trailing, nativeType, iconSize, medium, fullWidth, iconOnly, label, busy, loading, hovered, isPressed, isFocused, setHover, setPress, setFocus, children, style, onClick, rest }) {
41
+ if (typeof icon !== "string" || !icon) icon = "prophet-sparkle";
42
+ const ref = React.useRef(null);
43
+ const delay = React.useRef(aiSpinDelay()).current;
44
+ const on = !busy && hovered;
45
+ React.useEffect(() => rampAiSpin(ref.current, on), [on]);
46
+ const a = aiStyles({ on, press: !busy && isPressed, busy, focused: isFocused, medium, fullWidth, iconOnly, delay, style });
47
+ const sz = iconSize || 16, tier = iconSize ? null : tierIcon;
48
+ return (
49
+ <button ref={ref} type={nativeType} data-press="own" data-size={size} style={a.outer} disabled={busy} aria-busy={loading || undefined} aria-label={iconOnly ? label : undefined} onClick={onClick}
50
+ onMouseEnter={() => setHover(true)} onMouseLeave={() => { setHover(false); setPress(false); }}
51
+ onMouseDown={() => setPress(true)} onMouseUp={() => setPress(false)}
52
+ onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") setPress(true); }} onKeyUp={() => setPress(false)}
53
+ onFocus={(e) => { try { setFocus(e.target.matches(":focus-visible")); } catch (err) { setFocus(true); } }} onBlur={() => setFocus(false)} {...rest}>
54
+ <span aria-hidden="true" style={a.glow} />
55
+ <span aria-hidden="true" style={a.ring} />
56
+ <span style={a.surface}>
57
+ <span aria-hidden="true" style={a.tint} />
58
+ {loading ? <span style={a.iconBox}><Spinner size={iconSize} /></span> : icon ? (
59
+ <span style={a.iconBox}>
60
+ <Icon name={icon} size={sz} style={{ ...tier, ...a.iconRest }} />
61
+ <Icon name={icon} size={sz} gradient style={{ ...tier, ...a.iconGrad }} />
62
+ </span>
63
+ ) : null}
64
+ {iconOnly ? null : <span style={a.label}>{children}</span>}
65
+ {trailing}
66
+ </span>
67
+ </button>
68
+ );
69
+ }
70
+
71
+ /** Keybricks button. variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text | ai; size: sm 28 | md 32 | lg 40 — omit it to inherit the region's tier (the default). Interaction states: hover (one step darker) · press (two steps, set via mouse-down) · keyboard focus ring · loading · disabled. */
72
+ export function Button({ variant = "primary", size, icon, trailingIcon, iconSize, iconPosition = "left", medium, disabled, loading, fullWidth, iconOnly, label, text, nativeType = "button", hovered, pressed, focused, children, style, onClick, ...rest }) {
40
73
  const [hover, setHover] = React.useState(false);
41
74
  const [press, setPress] = React.useState(false);
42
75
  const [focus, setFocus] = React.useState(false);
43
76
  const busy = disabled || loading;
44
77
  const isPressed = press || pressed;
45
78
  const isFocused = focus || focused;
79
+ const glyph = (g) => (typeof g === "string" ? <Icon name={g} size={iconSize || 16} style={iconSize ? null : tierIcon} /> : g || null);
80
+ const tail = trailingIcon ? glyph(trailingIcon) : null;
81
+ const label_ = children ?? text;
82
+ if (variant === "ai") return <AiButton size={size} icon={icon} trailing={tail} nativeType={nativeType} iconSize={iconSize} medium={medium} fullWidth={fullWidth} iconOnly={iconOnly} label={label} busy={busy} loading={loading} hovered={hover || hovered} isPressed={isPressed} isFocused={isFocused} setHover={setHover} setPress={setPress} setFocus={setFocus} style={style} onClick={onClick} rest={rest}>{label_}</AiButton>;
46
83
  const s = {
47
84
  ...btnBase, ...variants[variant],
48
85
  ...(medium ? { fontWeight: "var(--weight-medium)" } : null),
@@ -57,23 +94,23 @@ export function Button({ variant = "primary", size, icon, iconSize, iconPosition
57
94
  if (isPressed) s.opacity = .7;
58
95
  } else if (isPressed) {
59
96
  s.background = presses[variant];
60
- } else if (hover) {
97
+ } else if (hover || hovered) {
61
98
  s.background = hovers[variant];
62
99
  }
63
100
  if (isFocused) s.boxShadow = "var(--ring-focus)";
64
101
  }
65
- const iconStyle = iconSize ? null : tierIcon;
66
- const leading = loading ? <Spinner size={iconSize} color={s.color} /> : (icon && iconPosition === "left" ? <Icon name={icon} size={iconSize || 16} style={iconStyle} /> : null);
67
- const trailing = !loading && icon && iconPosition === "right" ? <Icon name={icon} size={iconSize || 16} style={iconStyle} /> : null;
102
+ const leading = loading ? <Spinner size={iconSize} color={s.color} /> : (icon && iconPosition === "left" ? glyph(icon) : null);
103
+ const trailing = !loading && icon && iconPosition === "right" ? glyph(icon) : null;
68
104
  return (
69
- <button type="button" data-press="own" data-size={size} style={s} disabled={busy} aria-label={iconOnly ? label : undefined} onClick={onClick}
105
+ <button type={nativeType} data-press="own" data-size={size} style={s} disabled={busy} aria-busy={loading || undefined} aria-label={iconOnly ? label : undefined} onClick={onClick}
70
106
  onMouseEnter={() => setHover(true)} onMouseLeave={() => { setHover(false); setPress(false); }}
71
107
  onMouseDown={() => setPress(true)} onMouseUp={() => setPress(false)}
72
108
  onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") setPress(true); }} onKeyUp={() => setPress(false)}
73
109
  onFocus={(e) => { try { setFocus(e.target.matches(":focus-visible")); } catch (err) { setFocus(true); } }} onBlur={() => setFocus(false)} {...rest}>
74
110
  {leading}
75
- {iconOnly ? null : children}
111
+ {iconOnly ? null : label_}
76
112
  {trailing}
113
+ {tail}
77
114
  </button>
78
115
  );
79
116
  }