@keyfunc/keybricks-ds 2.3.1 → 2.4.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 (69) hide show
  1. package/DEPLOY.md +10 -4
  2. package/DESIGN.md +44 -1
  3. package/INTEGRATION.md +24 -6
  4. package/_adherence.oxlintrc.json +28 -13
  5. package/assets/logos/datasource/dat.png +0 -0
  6. package/assets/logos/datasource/excel.png +0 -0
  7. package/assets/logos/datasource/ftp.png +0 -0
  8. package/assets/logos/datasource/hierarchy.png +0 -0
  9. package/assets/logos/datasource/hive.png +0 -0
  10. package/assets/logos/datasource/json.png +0 -0
  11. package/assets/logos/datasource/mariadb.png +0 -0
  12. package/assets/logos/datasource/mongodb.png +0 -0
  13. package/assets/logos/datasource/mysql.png +0 -0
  14. package/assets/logos/datasource/odbc.png +0 -0
  15. package/assets/logos/datasource/oracle.png +0 -0
  16. package/assets/logos/datasource/postgresql.png +0 -0
  17. package/assets/logos/datasource/rest-api.png +0 -0
  18. package/assets/logos/datasource/sap-alt.png +0 -0
  19. package/assets/logos/datasource/sap.png +0 -0
  20. package/assets/logos/datasource/soap.png +0 -0
  21. package/assets/logos/datasource/sql-server.png +0 -0
  22. package/components/_cards/app-chrome.card.html +33 -21
  23. package/components/_cards/context-menu.card.html +7 -2
  24. package/components/_cards/icons-0-usage.card.html +88 -0
  25. package/components/_cards/icons-4-datasource.card.html +5 -0
  26. package/components/_cards/segmented-control.card.html +9 -3
  27. package/components/_cards/tabs.card.html +5 -2
  28. package/components/_shared/src-feedback.js +1 -1
  29. package/components/_shared/src-navigation.js +1 -1
  30. package/components/_shared/src-tabs.js +1 -1
  31. package/components/_shared/view-source.js +12 -12
  32. package/components/_vue/index.mjs +4 -1
  33. package/components/feedback/ContextMenu.d.ts +3 -2
  34. package/components/feedback/ContextMenu.jsx +8 -4
  35. package/components/feedback/ContextMenu.vue +9 -6
  36. package/components/feedback/Menu.d.ts +1 -1
  37. package/components/feedback/feedback.prompt.md +1 -1
  38. package/components/index.d.ts +1 -0
  39. package/components/index.js +4 -3
  40. package/components/navigation/SidePanel.d.ts +22 -0
  41. package/components/navigation/SidePanel.jsx +23 -0
  42. package/components/navigation/SidePanel.vue +21 -0
  43. package/components/navigation/Sidebar.d.ts +23 -7
  44. package/components/navigation/Sidebar.jsx +38 -37
  45. package/components/navigation/Sidebar.vue +40 -39
  46. package/components/navigation/TopBar.d.ts +28 -3
  47. package/components/navigation/TopBar.jsx +54 -7
  48. package/components/navigation/TopBar.vue +26 -8
  49. package/components/navigation/UserMenu.vue +27 -0
  50. package/components/navigation/chrome-tones.js +22 -0
  51. package/components/navigation/nav-model.js +21 -18
  52. package/components/navigation/navigation.prompt.md +2 -0
  53. package/components/tabs/CountBadge.vue +12 -0
  54. package/components/tabs/PanelTabs.vue +7 -3
  55. package/components/tabs/PillTabs.vue +7 -3
  56. package/components/tabs/SegmentedControl.d.ts +4 -2
  57. package/components/tabs/SegmentedControl.jsx +5 -3
  58. package/components/tabs/SegmentedControl.vue +4 -3
  59. package/components/tabs/Tabs.d.ts +14 -4
  60. package/components/tabs/Tabs.jsx +35 -20
  61. package/components/tabs/Tabs.vue +7 -3
  62. package/components/tabs/tabs.prompt.md +2 -2
  63. package/package.json +2 -2
  64. package/readme.md +4 -4
  65. package/readme.zh-CN.md +2 -2
  66. package/tokens/layout.css +4 -3
  67. package/tokens/tokens.d.ts +2 -0
  68. package/tokens/tokens.js +4 -1
  69. package/tokens/tokens.json +8 -1
package/DEPLOY.md CHANGED
@@ -55,11 +55,11 @@ deploys the documentation site. Watch it under *Build → Pipelines*.
55
55
  **5. Cut the first release.**
56
56
 
57
57
  ```bash
58
- git tag v2.3.1 && git push --tags
58
+ git tag v2.4.0 && git push --tags
59
59
  ```
60
60
 
61
61
  That tag runs `generated:check` → `version:check` → `npm publish`. Confirm under *Deploy →
62
- Package Registry*: `@keyfunc/keybricks-ds` 2.3.1.
62
+ Package Registry*: `@keyfunc/keybricks-ds` 2.4.0.
63
63
 
64
64
  **6. Hand developers two things:** the Pages URL, and the `.npmrc` snippet in §4 below.
65
65
 
@@ -112,7 +112,7 @@ import tokens from '@keyfunc/keybricks-ds/tokens.json' // cross-
112
112
  ```
113
113
 
114
114
  Deep imports are the better default in Vue and in any app that cares about tree-shaking —
115
- a barrel of 122 exports asks the bundler to prove each one is unused.
115
+ a barrel of 129 exports asks the bundler to prove each one is unused.
116
116
 
117
117
  ## 3. Publishing (maintainers)
118
118
 
@@ -130,6 +130,12 @@ git push && git push --tags
130
130
  `.gitlab-ci.yml` then runs: `generated:check` → `version:check` (tag must equal
131
131
  `package.json` version) → `npm publish` → `pages`.
132
132
 
133
+ **Changelog.** Every release gets an entry at the top of `changelog.js` (version, ISO date, and
134
+ items tagged `new` / `improved` / `fixed` / `breaking` / `removed`, each in English, Chinese and
135
+ Japanese). The documentation site renders it as the *Changelog* section under Overview. While a
136
+ version is still being built its entry carries `status: "unreleased"`; on release, drop the status
137
+ and set the date. Migration details stay in `INTEGRATION.md`; the changelog is the short summary.
138
+
133
139
  **Versioning policy.** A token's *value* changing is a minor; a token being *renamed or
134
140
  removed* is a major, because consumer code references `var(--name)` directly. Adding a
135
141
  component or a prop is a minor. Changing a component's default appearance is a major —
@@ -156,7 +162,7 @@ GitLab injects `CI_JOB_TOKEN` — use that instead and no secret is stored anywh
156
162
  **Install — straight from git** (no registry, no publish; works today)
157
163
 
158
164
  ```json
159
- "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.3.1"
165
+ "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.0"
160
166
  ```
161
167
 
162
168
  Because the package has no build step, a git dependency needs no `prepare` script — npm
package/DESIGN.md CHANGED
@@ -138,6 +138,48 @@ and menu row is the tier's control text; a `Badge` or `Tag` is its own small obj
138
138
  exception. In content, a secondary annotation (a timestamp beside a name, a count beside a label)
139
139
  may be `--text-xs`. Never put a 14px label and a 12px control on the same baseline.
140
140
 
141
+ ### Count badges on tabs and segments
142
+
143
+ A `Tabs`, `PanelTabs` or `PillTabs` tab, or a `SegmentedControl` option, may carry `count` (`{ label: "Submission", count: 3 }`).
144
+ It renders 6px right of the label as a small full-radius badge: `--count-h` tall (sm 16 · md 18 · lg 20),
145
+ 6px sides, **12px tabular figures in the UI font** (digits are Latin, so the size is not bumped in CJK).
146
+ The fill is the alpha wash `--neutral-alpha-8` — it darkens whatever it sits on by the same step, so the
147
+ badge reads on a white tab, on the #f5f5f5 active pill and in dark mode alike (a solid `--bg-hover` vanished on
148
+ the active pill). Selected is told from unselected by the text alone: `--text-heading` vs `--text-body` —
149
+ never `--text-secondary`, which fails 4.5:1 on a grey fill at 12px. Under the blue active underline tab the
150
+ badge stays neutral, so the blue is only the tab's own label and line. A count of 0 or none draws nothing; `showZero` (on the control, or per option) draws a muted
151
+ 0 so a filter set keeps its shape. The selected value is still matched by label — a count is decoration,
152
+ never identity. Counts never appear in `iconOnly` segments.
153
+
154
+ ### App chrome
155
+
156
+ The shell is `Sidebar` + optional `SidePanel` + `TopBar`, all 56px at the top edge. The rail is 240px (64px
157
+ collapsed), with a 56px brand head without a rule, a 2px-gap list of 36px items and a pinned footer
158
+ (Management, Knowledge Base) above a version line. Items that open a panel (`trailing: true`) carry a
159
+ 14px chevron; while a panel is open the rail collapses to its 64px icon form and the panel docks beside
160
+ it at 240px, in the **same tone**. Tones: `recessed` (default — grey rail, the active item is a white tile
161
+ with a hairline ring), `flush` (white rail), `contrast` (rail scoped to the dark theme) and `custom`
162
+ (dark scope on one of ten `SIDEBAR_COLORS`). Pick one tone per product and pass it to both
163
+ `Sidebar` and `SidePanel`. The top bar holds the bare search on the left and, on the right, the dark
164
+ AI Assistant button, notifications, help and the user trigger, which opens `UserMenu` (identity,
165
+ appearance switch, rows).
166
+
167
+ ### Menus: action vs single choice
168
+
169
+ `ContextMenu` and `Menu` serve two kinds of list, and the row spacing says which one it is.
170
+
171
+ - **Action menu** (Edit · Duplicate · Delete): no item is selected. Rows are flush (`gap: 0`); only the
172
+ hovered row is filled.
173
+ - **Single-choice menu** (section switcher, language picker): pass `selected: true` on the current item. It
174
+ gets a `--bg-hover` fill and `--weight-medium` `--text-heading` — **no checkmark**, the fill is the
175
+ mark. Rows are announced as `menuitemradio` with `aria-checked`. As soon as any item is selected the
176
+ rows are spaced **2px apart** (`gap: 2px`), the same rhythm as `Sidebar`, `Tree` and `DocTree`, so a
177
+ hovered row's fill never runs into the selected row's fill.
178
+
179
+ Dividers keep their 4px top and bottom margin in both kinds, independent of the row gap. Don't mix the
180
+ two: a menu that both switches state and runs actions puts the actions after a divider, and the choice
181
+ rows still carry `selected`.
182
+
141
183
  ### Table headers
142
184
 
143
185
  A header cell is the **same size as its data cells**, weight 600, `--text-secondary`, sentence
@@ -270,6 +312,7 @@ so two controls that share a row can never disagree. `md` is the default on `:ro
270
312
  | `--cell-px` | 8 | 12 | 16 | horizontal padding of a table cell |
271
313
  | `--check-size` | 14 | 16 | 16 | checkbox and radio box |
272
314
  | `--tag-h` | 20 | 24 | 28 | `Tag` |
315
+ | `--count-h` | 16 | 18 | 20 | count badge beside a segment / pill label |
273
316
  | `--switch-w` × `--switch-h` | 28 × 16 | 36 × 20 | 44 × 24 | `Switch` track |
274
317
 
275
318
  Where each tier belongs:
@@ -412,7 +455,7 @@ The list that prevents the most rework. These are absolute.
412
455
  - **Do not show a drop indicator where the drop is impossible.** No half-row boxes on a
413
456
  container, no insert rules on a node that can't take children.
414
457
  - **Do not build a new primitive when one exists.** Check the component index in
415
- `INTEGRATION.md` first — there are 117.
458
+ `INTEGRATION.md` first — there are 120.
416
459
  - **Do not use emoji as icons.** Use `<Icon name="..." />` (330 glyphs) or `<Unicon>` (1,206).
417
460
  - **Do not invent new component variants.** If `Button` lacks the variant you want, that is a
418
461
  design-system request, not a local override.
package/INTEGRATION.md CHANGED
@@ -31,7 +31,7 @@ Not using a registry yet? A git dependency works with no publishing at all — t
31
31
  has no build step, so npm can use the checkout as-is:
32
32
 
33
33
  ```json
34
- "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.3.1"
34
+ "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.0"
35
35
  ```
36
36
 
37
37
  **Source-only package.** `.jsx` and `.vue` files are published as authored, so your
@@ -162,7 +162,7 @@ Text, weight and tracking follow the tier through `--control-text` / `--control-
162
162
  weight becomes 600 and all tracking is 0 — nothing to configure. `DESIGN.md` has the full token
163
163
  table and the rules behind it.
164
164
 
165
- ## Component index (117 components)
165
+ ## Component index (120 components)
166
166
  Import path is `@keyfunc/keybricks-ds/components/<group>/<Name>.jsx` (React) or `.vue` (Vue 3).
167
167
  Every component has a sibling `.d.ts` with its full props contract, and each group has a
168
168
  `.prompt.md` with usage notes.
@@ -238,7 +238,9 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
238
238
  | Component | What it's for |
239
239
  | --- | --- |
240
240
  | `Sidebar` | The app's primary nav: collapsible sections, active state, footer items. `KEYBRICKS_NAV` is the product's real nav tree. |
241
+ | `SidePanel` | 240px panel docked beside the sidebar (Documents, Management), in the rail's tone. |
241
242
  | `TopBar` | Global header — search, notifications, theme toggle, avatar. |
243
+ | `UserMenu` | Account menu from the top bar's user trigger: identity, appearance switch, rows. |
242
244
  | `PageHeading` | Page title with optional subtitle and right-aligned actions. |
243
245
  | `PageToolbar` | Secondary bar under the heading: filters, view toggles, bulk actions. |
244
246
  | `Breadcrumb` | Path trail; collapses to an ellipsis dropdown past `maxItems`. |
@@ -249,9 +251,10 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
249
251
  ### `tabs/` — switching views
250
252
  | Component | What it's for |
251
253
  | --- | --- |
252
- | `Tabs` | Page-level underline tabs. |
253
- | `PanelTabs` | Compact tabs inside a detail panel or drawer. |
254
- | `PillTabs` | Filter/status tabs as pills. |
254
+ | `Tabs` | Page-level underline tabs; `{ label, count }` adds a count badge. |
255
+ | `PanelTabs` | Compact tabs inside a detail panel or drawer; `{ label, count }` adds a count badge. |
256
+ | `PillTabs` | Filter/status tabs as pills; `{ label, count }` adds a count badge. |
257
+ | `CountBadge` | The count badge beside a segment / pill label (`--count-h`, 12px tabular figures); used by `SegmentedControl` and `PillTabs`, exported for custom filter bars. |
255
258
  | `SegmentedControl` | Switches views of the same data, applies instantly — not a saved form value (`RadioGroup`) and not page navigation (`Tabs`). Reads the size tier, `icon` per option, `iconOnly`, per-option or whole-control `disabled`, `fullWidth`. ← → keyboard. |
256
259
  | `ViewToggle` | List/grid icon toggle. |
257
260
 
@@ -490,7 +493,7 @@ a migration note in the release. Token renames are major because your own code r
490
493
  Pin a minor range and update deliberately:
491
494
 
492
495
  ```json
493
- "@keyfunc/keybricks-ds": "~2.3.1"
496
+ "@keyfunc/keybricks-ds": "~2.4.0"
494
497
  ```
495
498
 
496
499
  ### Migrating from 1.x to 2.0
@@ -516,6 +519,21 @@ Pin a minor range and update deliberately:
516
519
  - **Tokens outside CSS.** `tokens.json` / `tokens.js` / `tokens.d.ts` gained the tier tokens
517
520
  (`--control-*`, `--bar-h`, `--group-gap`, …) — 352 light tokens in all (2.0.2 adds `--danger-soft-hover`).
518
521
 
522
+ ### 2.4.0
523
+
524
+ - **App Chrome matches the product.** `Sidebar`: 56px brand head without a rule, `--text-md` brand, a 2px-gap list, `trailing: true` items with a chevron (they open a side panel), Collapse / Expand item (chevron-left / panel-left), pinned footer and an optional `version` line. New `tone` prop — `"recessed"` (grey rail, active item a white tile with a hairline ring; **the new default**), `"flush"` (the old white look), `"contrast"` (dark-scoped rail), `"custom"` + `color` (`SIDEBAR_COLORS` or any hex). `KEYBRICKS_NAV` is the product's nav (Home … Task Execution); new `KEYBRICKS_NAV_FOOTER` (Management, Knowledge Base) is the default footer (was Help / Settings / Dark Mode) and `KEYBRICKS_MGMT` holds the Management panel groups. To keep the old look pass `tone="flush"` and your own `footerItems`.
525
+ - **Data-source logos:** 17 logo images (databases, SAP, protocols, file types) in `assets/logos/datasource/<id>.png`, 500×500 PNG, listed on the new *Data Source Logos* icons page. They are plain images, not `Icon` glyphs — use them with `<img>`.
526
+ - **New `SidePanel`**: the 240px panel docked beside the sidebar (Documents, Management), same tone as the rail, opens from 0 width with a fade; 56px header with title, actions and close.
527
+ - **`TopBar`**: dark **AI Assistant** (`ai` icon) first, then notifications and help `IconButton`s, then a user trigger (`user={{ name, email }}`) that opens a menu. The bell is no longer `lg`. **New `UserMenu`**: avatar, name, email, an Appearance row (icon-only light / dark / system) and menu rows.
528
+
529
+ - **`ContextMenu` / `Menu` single-choice state.** Items accept `selected: true` (section switcher, language picker): `--bg-hover` fill, `--weight-medium` `--text-heading`, no checkmark; rows become `menuitemradio` with `aria-checked`. When any item is selected the rows are spaced 2px apart (as in `Sidebar`, `Tree`, `DocTree`); action menus with nothing selected keep flush rows. Dividers keep 4px above and below.
530
+
531
+ - **Docs:** new *Icon Usage* page at the top of Icons (monochrome vs coloured glyphs: IconTile hues, Tree/DocTree document hues, status, marks). The documentation site itself now follows the system rules — neutral selected segments, sentence-case labels, sm-tier toolbar controls, 32px Sidebar-spec nav rows.
532
+ ### 2.3.2
533
+
534
+ - **Counts on `Tabs`, `PanelTabs`, `PillTabs` and `SegmentedControl`.** Tabs / options accept `{ label, count?, showZero? }`; a count renders as a `CountBadge` 6px right of the label — `--count-h` tall (new tier token: sm 16 · md 18 · lg 20), 6px sides, full radius, 12px tabular figures; fill `--neutral-alpha-8` on any surface, text `--text-body`, `--text-heading` when selected. 0 or no count hides it; `showZero` (control or option) draws a muted 0. Selection is still matched by label. `CountBadge` is exported (React `Tabs.jsx`, Vue `CountBadge.vue`).
535
+ - Additive only.
536
+
519
537
  ### 2.3.1
520
538
 
521
539
  - **`Table` row open, tree rows and labels.** `onRowOpen(row, index, event)` (Vue `@row-open`) makes the icon tile and title of a two-line cell open the row (hand cursor; Enter / Space on the title). `tree` with nested `children` (or `hasChildren` for lazy rows) indents the two-line column 24px per level with an expand arrow — `expandedKeys` / `defaultExpandedKeys` / `onExpandChange(row, expanded, keys)` (Vue `v-model:expandedKeys`, `@expand-change`), `rowKey` (default `id`); sorting reorders siblings only. `labels: { sortAsc, sortDesc, sortClear, more, expand, collapse }` translates every word the table draws (English by default). Indices reported by selection and callbacks are depth-first over all rows — unchanged for flat tables.
@@ -17,6 +17,7 @@
17
17
  {
18
18
  "group": [
19
19
  "assets/icons/**",
20
+ "changelog.js",
20
21
  "components/**",
21
22
  "components/_shared/**",
22
23
  "components/buttons/**",
@@ -29,6 +30,7 @@
29
30
  "components/navigation/**",
30
31
  "components/pipeline/**",
31
32
  "components/tabs/**",
33
+ "covers.js",
32
34
  "site-nav.js",
33
35
  "tokens/**"
34
36
  ],
@@ -428,8 +430,8 @@
428
430
  "message": "<SearchInput> size must be one of 'sm' | 'md' | 'lg'."
429
431
  },
430
432
  {
431
- "selector": "JSXOpeningElement[name.name='SegmentedControl'] > JSXAttribute > JSXIdentifier[name!=/^(?:options|value|onChange|size|iconOnly|disabled|fullWidth|style|key|ref|className|style|children)$/]",
432
- "message": "<SegmentedControl> doesn't accept that prop. Declared props: options, value, onChange, size, iconOnly, disabled, fullWidth, style."
433
+ "selector": "JSXOpeningElement[name.name='SegmentedControl'] > JSXAttribute > JSXIdentifier[name!=/^(?:options|value|onChange|size|iconOnly|disabled|fullWidth|showZero|style|key|ref|className|style|children)$/]",
434
+ "message": "<SegmentedControl> doesn't accept that prop. Declared props: options, value, onChange, size, iconOnly, disabled, fullWidth, showZero, style."
433
435
  },
434
436
  {
435
437
  "selector": "JSXOpeningElement[name.name='SegmentedControl'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]",
@@ -444,8 +446,20 @@
444
446
  "message": "<Select> size must be one of 'sm' | 'md' | 'lg'."
445
447
  },
446
448
  {
447
- "selector": "JSXOpeningElement[name.name='SidebarItem'] > JSXAttribute > JSXIdentifier[name!=/^(?:icon|label|children|open|key|ref|className|style|children)$/]",
448
- "message": "<SidebarItem> doesn't accept that prop. Declared props: icon, label, children, open."
449
+ "selector": "JSXOpeningElement[name.name='SidebarItem'] > JSXAttribute > JSXIdentifier[name!=/^(?:icon|label|trailing|children|key|ref|className|style|children)$/]",
450
+ "message": "<SidebarItem> doesn't accept that prop. Declared props: icon, label, trailing, children."
451
+ },
452
+ {
453
+ "selector": "JSXOpeningElement[name.name='SidePanel'] > JSXAttribute > JSXIdentifier[name!=/^(?:open|title|actions|onClose|closeLabel|closeIcon|tone|color|width|size|children|style|key|ref|className|style|children)$/]",
454
+ "message": "<SidePanel> doesn't accept that prop. Declared props: open, title, actions, onClose, closeLabel, closeIcon, tone, color, width, size, children, style."
455
+ },
456
+ {
457
+ "selector": "JSXOpeningElement[name.name='SidePanel'] > JSXAttribute[name.name='tone'] > Literal[value!=/^(?:recessed|flush|contrast|custom)$/]",
458
+ "message": "<SidePanel> tone must be one of 'recessed' | 'flush' | 'contrast' | 'custom'."
459
+ },
460
+ {
461
+ "selector": "JSXOpeningElement[name.name='SidePanel'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]",
462
+ "message": "<SidePanel> size must be one of 'sm' | 'md' | 'lg'."
449
463
  },
450
464
  {
451
465
  "selector": "JSXOpeningElement[name.name='Slider'] > JSXAttribute > JSXIdentifier[name!=/^(?:value|min|max|step|labels|showValue|unit|disabled|width|onChange|style|key|ref|className|style|children)$/]",
@@ -512,12 +526,8 @@
512
526
  "message": "<DataTableTwoLine> doesn't accept that prop. Declared props: icon, hue, description, mark, more."
513
527
  },
514
528
  {
515
- "selector": "JSXOpeningElement[name.name='Tabs'] > JSXAttribute > JSXIdentifier[name!=/^(?:tabs|active|onChange|size|style|key|ref|className|style|children)$/]",
516
- "message": "<Tabs> doesn't accept that prop. Declared props: tabs, active, onChange, size, style."
517
- },
518
- {
519
- "selector": "JSXOpeningElement[name.name='Tabs'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]",
520
- "message": "<Tabs> size must be one of 'sm' | 'md' | 'lg'."
529
+ "selector": "JSXOpeningElement[name.name='TabItem'] > JSXAttribute > JSXIdentifier[name!=/^(?:label|count|showZero|key|ref|className|style|children)$/]",
530
+ "message": "<TabItem> doesn't accept that prop. Declared props: label, count, showZero."
521
531
  },
522
532
  {
523
533
  "selector": "JSXOpeningElement[name.name='Tag'] > JSXAttribute > JSXIdentifier[name!=/^(?:children|size|onRemove|tone|style|key|ref|className|style|children)$/]",
@@ -540,8 +550,8 @@
540
550
  "message": "<TimelineItem> tone must be one of 'success' | 'danger' | 'warning' | 'info' | 'neutral'."
541
551
  },
542
552
  {
543
- "selector": "JSXOpeningElement[name.name='TopBar'] > JSXAttribute > JSXIdentifier[name!=/^(?:placeholder|actions|onSearch|style|key|ref|className|style|children)$/]",
544
- "message": "<TopBar> doesn't accept that prop. Declared props: placeholder, actions, onSearch, style."
553
+ "selector": "JSXOpeningElement[name.name='TopBar'] > JSXAttribute > JSXIdentifier[name!=/^(?:placeholder|onSearch|actions|aiLabel|onAi|onNotifications|onHelp|notificationsLabel|helpLabel|user|userMenu|style|key|ref|className|style|children)$/]",
554
+ "message": "<TopBar> doesn't accept that prop. Declared props: placeholder, onSearch, actions, aiLabel, onAi, onNotifications, onHelp, notificationsLabel, helpLabel, user, userMenu, style."
545
555
  },
546
556
  {
547
557
  "selector": "JSXOpeningElement[name.name='TreeItem'] > JSXAttribute > JSXIdentifier[name!=/^(?:key|title|label|icon|hue|count|tag|children|disabled|disableCheckbox|selectable|checkable|isLeaf|key|ref|className|style|children)$/]",
@@ -799,6 +809,9 @@
799
809
  "SidebarItem": {
800
810
  "replaces": []
801
811
  },
812
+ "SidePanel": {
813
+ "replaces": []
814
+ },
802
815
  "Slider": {
803
816
  "replaces": []
804
817
  },
@@ -826,7 +839,7 @@
826
839
  "DataTableTwoLine": {
827
840
  "replaces": []
828
841
  },
829
- "Tabs": {
842
+ "TabItem": {
830
843
  "replaces": []
831
844
  },
832
845
  "Tag": {
@@ -949,6 +962,7 @@
949
962
  "--control-px",
950
963
  "--control-text",
951
964
  "--control-tracking",
965
+ "--count-h",
952
966
  "--cyan-100",
953
967
  "--cyan-200",
954
968
  "--cyan-300",
@@ -1530,6 +1544,7 @@
1530
1544
  "--cell-px": "spacing",
1531
1545
  "--check-size": "spacing",
1532
1546
  "--tag-h": "spacing",
1547
+ "--count-h": "spacing",
1533
1548
  "--switch-w": "spacing",
1534
1549
  "--switch-h": "spacing",
1535
1550
  "--title-cell-py": "spacing",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x510" name="App Chrome" subtitle="Sidebar · TopBar · PageToolbar · DocTree" -->
1
+ <!-- @dsCard group="Components" viewport="700x1490" name="App Chrome" subtitle="Sidebar tones · SidePanel · TopBar · UserMenu · PageToolbar" -->
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>
@@ -9,28 +9,40 @@
9
9
  <style>body{margin:0;background:var(--bg-app);font-family:var(--font-sans);overflow:hidden;box-sizing:border-box;padding:18px}</style></head>
10
10
  <body><div id="root"></div>
11
11
  <script type="text/babel">
12
- const { Sidebar, KEYBRICKS_NAV, TopBar, PageToolbar, DocTree, Button } = window.KeybricksDesignSystem_71231a;
12
+ const { Sidebar, SidePanel, TopBar, UserMenu, PageToolbar, SegmentedControl, SearchInput, Tree, Button, IconButton, SIDEBAR_COLORS, KEYBRICKS_MGMT } = window.KeybricksDesignSystem_71231a;
13
+ const LOGO = "../../assets/keybricks-logo.png";
13
14
  function Sheet(){
14
- const [active,setActive]=React.useState("Home");
15
- const [col,setCol]=React.useState(false);
16
- return <div style={{display:"flex",height:420,overflow:"hidden"}}>
17
- <Sidebar items={KEYBRICKS_NAV} active={active} onSelect={setActive} collapsed={col} onToggleCollapse={()=>setCol(!col)} logo="../../assets/keybricks-logo.png"/>
18
- <DocTree active="Financial Analysis" items={[
19
- {label:"Q1 2026",kind:"folder"},{label:"Q4 2025",kind:"folder"},
20
- {label:"Financial Analysis",hue:"orange"},{label:"Revenue Analysis",hue:"green"},
21
- {label:"Budget 2026",kind:"folder"},{label:"Monthly Journal",hue:"purple",icon:"book-open"},{label:"Variance Report",hue:"blue"},
22
- ]}/>
23
- <div style={{flex:1,display:"flex",flexDirection:"column",minWidth:0}}>
24
- <TopBar/>
25
- <PageToolbar title="Oracle Adapter" onBack={()=>{}} actions={<React.Fragment>
26
- <Button variant="ghost" icon="settings-2">Custom Parameters</Button>
27
- <Button variant="ghost" icon="play">Run Test</Button>
28
- <Button icon="send">Submit</Button>
29
- </React.Fragment>}/>
30
- <div style={{flex:1}}></div>
31
- </div>
15
+ const [active,setActive]=React.useState("Home"),[col,setCol]=React.useState(true),[panel,setPanel]=React.useState(null),[tab,setTab]=React.useState("All"),[theme,setTheme]=React.useState("System");
16
+ const pick=(l)=>{ if(l==="Documents"||l==="Management"){ setPanel(panel===l?null:l); } else { setActive(l); setPanel(null); } };
17
+ return <div style={{display:"flex",flexDirection:"column",gap:28}}>
18
+ <section><div className="kb-h">Shell — Sidebar (recessed) · SidePanel · TopBar</div>
19
+ <div style={{overflowX:"auto",border:"1px solid var(--border-card)",borderRadius:"var(--radius-lg)"}}><div style={{display:"flex",height:580,minWidth:800,overflow:"hidden"}}>
20
+ <Sidebar logo={LOGO} version="Version 2.4.0" active={panel||active} onSelect={pick} collapsed={col||!!panel} onToggleCollapse={()=>setCol(!col)}/>
21
+ <SidePanel open={!!panel} title={panel} size="sm" onClose={()=>setPanel(null)} actions={panel==="Documents"?<IconButton icon="maximize-2" muted label="Open page"/>:null}>
22
+ {panel==="Documents"?<div style={{display:"flex",flexDirection:"column",gap:8,padding:"0 16px 8px"}}>
23
+ <SegmentedControl fullWidth options={["Recent","Favorites","All"]} value={tab} onChange={setTab}/>
24
+ <SearchInput bare placeholder="Search documents..."/>
25
+ <Tree showIcon defaultExpanded={["b"]} items={[{key:"b",title:"Budget 2026",children:[{key:"b1",title:"Opex by Department"},{key:"b2",title:"Headcount Plan"}]},{key:"c",title:"Consolidation"}]}/>
26
+ </div>:<div style={{display:"flex",flexDirection:"column",gap:2,padding:8}}>{KEYBRICKS_MGMT.map(g=><div key={g.label} style={{height:36,display:"flex",alignItems:"center",gap:10,padding:"0 10px",fontSize:"var(--text-base)",color:"var(--text-body)"}}>{g.label}</div>)}</div>}
27
+ </SidePanel>
28
+ <div style={{flex:1,display:"flex",flexDirection:"column",minWidth:0}}>
29
+ <TopBar user={{name:"Felix Wang",email:"felix@keyfunc.com"}} userMenu={<UserMenu name="Felix Wang" email="felix@keyfunc.com" theme={theme} onThemeChange={setTheme} items={[{label:"Profile",icon:"user"},{label:"Settings",icon:"settings"},{divider:true},{label:"Sign out",icon:"log-out",danger:true}]}/>}/>
30
+ <PageToolbar title="Oracle Adapter" onBack={()=>{}} actions={<Button icon="send">Submit</Button>}/>
31
+ <div style={{flex:1,background:"var(--bg-app)"}}></div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ <div className="note">The shell is 800px wide and scrolls sideways in this card; it starts with the rail collapsed — expand it with the first item. Documents and Management carry a trailing chevron: they open a SidePanel, and the rail collapses to 64px while it is open. The user trigger opens a UserMenu with the appearance switch.</div>
36
+ </section>
37
+ <section><div className="kb-h">Tones — tone="recessed" (default) · "flush" · "contrast" · "custom"</div>
38
+ <div style={{display:"flex",gap:12,flexWrap:"wrap"}}>
39
+ {[["recessed"],["flush"],["contrast"],["custom","navy"],["custom","forest"],["custom","plum"]].map(([t,cl])=><div key={t+(cl||"")} style={{display:"flex",flexDirection:"column",gap:6}}><span className="lbl">{t}{cl?" · "+cl:""}</span>
40
+ <div style={{height:300,overflow:"hidden",border:"1px solid var(--border-card)",borderRadius:"var(--radius-md)"}}><Sidebar logo={LOGO} tone={t} color={cl} active="Documents" version="Version 2.4.0" items={[{icon:"house",label:"Home"},{icon:"file-text",label:"Documents",trailing:true},{icon:"database",label:"Data Integration"}]} footerItems={[{icon:"settings",label:"Management",trailing:true}]} style={{width:200}}/></div></div>)}
41
+ </div>
42
+ <div className="note">Custom colours: {Object.keys(SIDEBAR_COLORS).join(" · ")} — or any hex. Contrast and custom rails are scoped to the dark theme, so the components inside follow.</div>
43
+ </section>
32
44
  </div>;
33
45
  }
34
46
  ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
35
- if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"Sidebar.jsx","path":"../navigation/Sidebar.jsx"},{"name":"TopBar.jsx","path":"../navigation/TopBar.jsx"},{"name":"DocTree.jsx","path":"../navigation/DocTree.jsx"},{"name":"Button.jsx","path":"../buttons/Button.jsx"}]); }, 0);
47
+ if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"Sidebar.jsx","path":"../navigation/Sidebar.jsx"},{"name":"SidePanel.jsx","path":"../navigation/SidePanel.jsx"},{"name":"TopBar.jsx","path":"../navigation/TopBar.jsx","vue":"../navigation/TopBar.vue","vueName":"TopBar.vue"},{"name":"UserMenu","path":"../navigation/TopBar.jsx","vue":"../navigation/UserMenu.vue","vueName":"UserMenu.vue"},{"name":"chrome-tones.js","path":"../navigation/chrome-tones.js","reactOnly":false}]); }, 0);
36
48
  </script></body></html>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x920" name="Context Menu" subtitle="Icons · plain text · danger item · long list" -->
1
+ <!-- @dsCard group="Components" viewport="700x920" name="Context Menu" subtitle="Action vs single-choice spacing · selected state · icons · plain text · danger item · long list" -->
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>
@@ -12,7 +12,12 @@
12
12
  <script type="text/babel">
13
13
  const { ContextMenu } = window.KeybricksDesignSystem_71231a;
14
14
  const { KBTiers: Tiers } = window;
15
- function Sheet(){return <div className="grid">
15
+ function Sheet(){const [sec,setSec]=React.useState("Overview");const [lang,setLang]=React.useState("English");return <div className="grid">
16
+ <div style={{flexBasis:"100%",display:"flex",flexDirection:"column",gap:8}}><span className="lbl">Row spacing — an action menu keeps its rows flush; a single-choice menu (one item <code>selected</code>) spaces them 2px apart, so a hovered row never touches the selected one</span><div style={{display:"flex",gap:20,alignItems:"flex-start",flexWrap:"wrap"}}>
17
+ <div className="col"><span className="lbl">Action menu · gap 0</span><ContextMenu items={[{label:"Edit",icon:"edit-2"},{label:"Duplicate",icon:"copy"},{label:"Export",icon:"download"},{divider:true},{label:"Delete",icon:"trash-2",danger:true}]}/></div>
18
+ <div className="col"><span className="lbl">Single choice · gap 2 · no checkmark</span><ContextMenu items={["Overview","Data Sources","Pipelines","Reports"].map(l=>({label:l,selected:l===sec}))} onSelect={setSec}/></div>
19
+ <div className="col"><span className="lbl">Language picker</span><ContextMenu items={[{label:"English",selected:lang==="English"},{label:"简体中文",selected:lang==="简体中文"},{label:"日本語",selected:lang==="日本語"},{divider:true},{label:"Follow system",icon:"monitor"}]} onSelect={(l)=>l!=="Follow system"&&setLang(l)}/></div>
20
+ </div></div>
16
21
  <div className="col"><span className="lbl">With icons</span><ContextMenu items={[{label:"Preview",icon:"eye"},{label:"Duplicate",icon:"copy"},{divider:true},{label:"Delete",icon:"trash-2",danger:true}]}/></div>
17
22
  <div className="col"><span className="lbl">Plain text, no icons</span><ContextMenu items={[{label:"Rename"},{label:"Move to..."},{label:"Archive"}]}/></div>
18
23
  <div className="col"><span className="lbl">Danger only</span><ContextMenu items={[{label:"Remove from workflow",icon:"x",danger:true}]}/></div>
@@ -0,0 +1,88 @@
1
+ <!-- @dsCard group="Icons" viewport="700x2250" name="Icon Usage" subtitle="Monochrome glyphs · category tiles · document hues · status · marks · icons inside components — when an icon gets colour" -->
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/card-kit.js"></script>
9
+ <script src="../_shared/view-source.js"></script>
10
+ <style>body{margin:0;background:var(--bg-surface)}.cap{display:flex;flex-direction:column;align-items:center;gap:6px;min-width:56px}.cap span{font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary);white-space:nowrap}.rules{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:6px;font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-body)}.rules code{font-size:12px;white-space:nowrap}</style></head>
11
+ <body><div id="root"></div>
12
+ <script type="text/babel">
13
+ const { Icon, IconTile, StatusBadge, Alert, Button, IconButton, PaletteItem, ListRow, ActivityItem, DocTree } = window.KeybricksDesignSystem_71231a;
14
+ const LEAF = [["orange", "var(--cat-transform)"], ["green", "var(--cat-source)"], ["purple", "var(--cat-merge)"], ["blue", "var(--info-fg)"], ["teal", "var(--cat-output)"], ["gray", "var(--text-muted)"]];
15
+ const HUES = [["source", "table"], ["join", "shuffle"], ["transform", "funnel"], ["merge", "merge"], ["output", "layers"]];
16
+ const Cap = ({ label, children }) => <div className="cap">{children}<span>{label}</span></div>;
17
+ const Glyph = ({ name, color, label }) => <Cap label={label}><span style={{ color, display: "inline-flex" }}><Icon name={name} size={20} /></span></Cap>;
18
+ function Sheet() {
19
+ return <div className="kb-sheet">
20
+ <KBSec title="1 · Monochrome glyph — the default" note="Almost every icon is a single-colour stroke glyph that takes the colour of its text. Colour comes from the text token, never from the icon itself.">
21
+ <KBRow k="text colour">
22
+ <Glyph name="file-text" color="var(--text-heading)" label="heading" />
23
+ <Glyph name="file-text" color="var(--text-body)" label="body" />
24
+ <Glyph name="file-text" color="var(--text-secondary)" label="secondary" />
25
+ <Glyph name="file-text" color="var(--text-muted)" label="muted / disabled" />
26
+ <Glyph name="file-text" color="var(--action-primary)" label="action" />
27
+ </KBRow>
28
+ <KBRow k="on a fill"><Button icon="download">Export</Button><Button variant="dark" icon="ai-sparkles">AI Assistant</Button><Button variant="danger" icon="trash-2">Delete</Button><IconButton icon="ellipsis" label="More" /></KBRow>
29
+ </KBSec>
30
+ <KBSec title="2 · Category tile — IconTile" note="The coloured square is a category, not decoration: each hue is one node type (--cat-*-bg behind --cat-* glyph). Dark mode swaps both for a low-alpha wash automatically.">
31
+ <KBRow k="palette · 40 r14">{HUES.map(([h, i]) => <Cap key={h} label={h}><IconTile icon={i} hue={h} /></Cap>)}</KBRow>
32
+ <KBRow k="node · 48 r16">{HUES.map(([h, i]) => <Cap key={h} label={h}><IconTile icon={i} hue={h} kind="node" /></Cap>)}</KBRow>
33
+ <KBRow k="row · 32 r8">
34
+ <Cap label="hue"><IconTile icon="table" hue="join" kind="row" /></Cap>
35
+ <Cap label="hue"><IconTile icon="box" hue="merge" kind="row" /></Cap>
36
+ <Cap label="neutral"><IconTile icon="folder" hue="neutral" kind="row" /></Cap>
37
+ <Cap label="no hue (hairline)"><IconTile icon="file-text" kind="row" /></Cap>
38
+ </KBRow>
39
+ </KBSec>
40
+ <KBSec title="3 · Document glyph — Tree / DocTree hue" note="The one place a bare glyph is coloured: a document leaf in Tree or DocTree takes a hue that tells the document type apart. Folders stay muted. Pass hue on the item; the component maps it to the token.">
41
+ <KBRow k="leaf hue">{LEAF.map(([h, v]) => <Cap key={h} label={h}><span style={{ color: v, display: "inline-flex" }}><Icon name="file-text" size={16} /></span></Cap>)}</KBRow>
42
+ <KBRow k="DocTree" style={{ display: "block" }}><div style={{ width: 240, border: "1px solid var(--border-card)", borderRadius: "var(--radius-lg)", overflow: "hidden" }}><DocTree title="Documents" active="Revenue Analysis" items={[{ label: "Q1 2026", kind: "folder" }, { label: "Revenue Analysis", hue: "green" }, { label: "Cost Allocation", hue: "orange" }, { label: "FX Rates Model", hue: "purple" }, { label: "Cash Flow Forecast", hue: "blue" }, { label: "Notes", hue: "gray" }]} /></div></KBRow>
43
+ </KBSec>
44
+ <KBSec title="4 · Status" note="Status colour is reserved for state: success, warning, danger, info, pending. The glyph sits in a solid circle (StatusBadge) or leads a tinted message (Alert).">
45
+ <KBRow k="StatusBadge">
46
+ <Cap label="success"><StatusBadge tone="success" icon="check" /></Cap>
47
+ <Cap label="danger"><StatusBadge tone="danger" icon="x" /></Cap>
48
+ <Cap label="warning"><StatusBadge tone="warning" icon="bell" /></Cap>
49
+ <Cap label="pending"><StatusBadge tone="pending" icon="clock" /></Cap>
50
+ <Cap label="info"><StatusBadge tone="info" icon="info" /></Cap>
51
+ <Cap label="node dot · 13"><StatusBadge tone="danger" size={13} /></Cap>
52
+ </KBRow>
53
+ <KBRow k="Alert" style={{ flexDirection: "column", alignItems: "stretch", gap: 8 }}>
54
+ <Alert tone="info" title="Import scheduled">Runs daily at 06:00.</Alert>
55
+ <Alert tone="success" title="Sync complete">312 rows updated.</Alert>
56
+ <Alert tone="warning" title="Mapping incomplete">2 accounts have no target.</Alert>
57
+ <Alert tone="danger" title="Connection failed">The Oracle source did not respond.</Alert>
58
+ </KBRow>
59
+ </KBSec>
60
+ <KBSec title="5 · Marks" note="A 14px mark next to a title flags one property of the row. Favourite is the only filled glyph in the system.">
61
+ <KBRow k="favourite">
62
+ <span style={{ display: "inline-flex", alignItems: "center", gap: 4, fontSize: "var(--text-base)", color: "var(--text-heading)" }}>FX Rates Model<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinejoin="round" style={{ color: "var(--warning)" }}><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg></span>
63
+ <span style={{ fontSize: "var(--text-xs)", color: "var(--text-secondary)" }}>Table <code>mark: "favorite"</code> · --warning</span>
64
+ </KBRow>
65
+ <KBRow k="AI">
66
+ <Glyph name="ai" color="var(--action-primary)" label="ai" />
67
+ <Glyph name="ai-sparkles" color="var(--action-primary)" label="ai-sparkles" />
68
+ </KBRow>
69
+ </KBSec>
70
+ <KBSec title="6 · Inside components" note="These components choose the tile and colour for you — pass an icon and, where it applies, a hue.">
71
+ <KBRow k="PaletteItem"><div style={{ display: "flex", gap: 8 }}><PaletteItem icon="table" hue="source" label="Detail Sheet" /><PaletteItem icon="shuffle" hue="join" label="Association Match" /><PaletteItem icon="funnel" hue="transform" label="Filter" /><PaletteItem icon="layers" hue="output" label="Output" /></div></KBRow>
72
+ <KBRow k="ActivityItem" style={{ display: "block" }}><ActivityItem icon="upload" title="Opening balances imported" meta="Felix Wang · 09:10" action="Open" /></KBRow>
73
+ <KBRow k="ListRow" style={{ display: "block" }}><ListRow icon="workflow" title="Month-end close" meta="6 steps" trailing="6" /></KBRow>
74
+ </KBSec>
75
+ <KBSec title="Rules">
76
+ <ul className="rules">
77
+ <li>Default to a monochrome glyph in the text's own colour. Add colour only when it means something.</li>
78
+ <li>Category hue → <code>IconTile</code> (or a component that uses it). A bare glyph gets a hue only as a document leaf, through the <code>hue</code> of a Tree or DocTree item.</li>
79
+ <li>Status colour only for state, always with a shape (circle, tinted message) — never colour alone.</li>
80
+ <li>Tile and glyph always come as a pair: <code>--cat-x-bg</code> behind <code>--cat-x</code>. Don't mix hues or invent tints.</li>
81
+ <li>Glyphs on a solid fill (buttons, status badges) use <code>--on-accent</code>; components do this for you.</li>
82
+ </ul>
83
+ </KBSec>
84
+ </div>;
85
+ }
86
+ ReactDOM.createRoot(document.getElementById("root")).render(<Sheet />);
87
+ if (window.KBViewSource) setTimeout(function () { KBViewSource.mount("#root", [{ "name": "Badge.jsx (IconTile · StatusBadge)", "path": "../display/Badge.jsx", "vue": "../display/IconTile.vue", "vueName": "IconTile.vue" }, { "name": "Icon.jsx", "path": "../icons/Icon.jsx" }]); }, 0);
88
+ </script></body></html>
@@ -0,0 +1,5 @@
1
+ <!-- @dsCard group="Icons" viewport="700x660" name="Data Source Logos" subtitle="17 data-source logo images in assets/logos/datasource/" -->
2
+ <!DOCTYPE html>
3
+ <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css">
4
+ <style>body{margin:0;background:var(--bg-surface);padding:24px;box-sizing:border-box;font-family:var(--font-sans)}.g{display:grid;grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:12px}.t{display:flex;flex-direction:column;align-items:center;gap:8px;padding:12px 8px;border:1px solid var(--border-card);border-radius:var(--radius-lg)}.t img{width:64px;height:64px;border-radius:var(--radius-lg);display:block}.n{font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-heading);text-align:center}.f{font-size:11px;line-height:16px;color:var(--text-secondary)}</style></head>
5
+ <body><div class="g"><div class="t"><img src="../../assets/logos/datasource/oracle.png" alt="Oracle"><span class="n">Oracle</span><span class="f">oracle.png</span></div><div class="t"><img src="../../assets/logos/datasource/sap.png" alt="SAP"><span class="n">SAP</span><span class="f">sap.png</span></div><div class="t"><img src="../../assets/logos/datasource/sap-alt.png" alt="SAP (alternate)"><span class="n">SAP (alternate)</span><span class="f">sap-alt.png</span></div><div class="t"><img src="../../assets/logos/datasource/sql-server.png" alt="SQL Server"><span class="n">SQL Server</span><span class="f">sql-server.png</span></div><div class="t"><img src="../../assets/logos/datasource/mysql.png" alt="MySQL"><span class="n">MySQL</span><span class="f">mysql.png</span></div><div class="t"><img src="../../assets/logos/datasource/mariadb.png" alt="MariaDB"><span class="n">MariaDB</span><span class="f">mariadb.png</span></div><div class="t"><img src="../../assets/logos/datasource/postgresql.png" alt="PostgreSQL"><span class="n">PostgreSQL</span><span class="f">postgresql.png</span></div><div class="t"><img src="../../assets/logos/datasource/mongodb.png" alt="MongoDB"><span class="n">MongoDB</span><span class="f">mongodb.png</span></div><div class="t"><img src="../../assets/logos/datasource/hive.png" alt="Hive"><span class="n">Hive</span><span class="f">hive.png</span></div><div class="t"><img src="../../assets/logos/datasource/odbc.png" alt="ODBC"><span class="n">ODBC</span><span class="f">odbc.png</span></div><div class="t"><img src="../../assets/logos/datasource/rest-api.png" alt="REST API"><span class="n">REST API</span><span class="f">rest-api.png</span></div><div class="t"><img src="../../assets/logos/datasource/soap.png" alt="SOAP"><span class="n">SOAP</span><span class="f">soap.png</span></div><div class="t"><img src="../../assets/logos/datasource/ftp.png" alt="FTP"><span class="n">FTP</span><span class="f">ftp.png</span></div><div class="t"><img src="../../assets/logos/datasource/excel.png" alt="Excel"><span class="n">Excel</span><span class="f">excel.png</span></div><div class="t"><img src="../../assets/logos/datasource/json.png" alt="JSON"><span class="n">JSON</span><span class="f">json.png</span></div><div class="t"><img src="../../assets/logos/datasource/dat.png" alt="DAT"><span class="n">DAT</span><span class="f">dat.png</span></div><div class="t"><img src="../../assets/logos/datasource/hierarchy.png" alt="Hierarchy"><span class="n">Hierarchy</span><span class="f">hierarchy.png</span></div></div></body></html>
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x870" name="Segmented Control" subtitle="Mutually exclusive views in one inset track — md/sm · icons · icon-only · disabled option · full width · ViewToggle" -->
1
+ <!-- @dsCard group="Components" viewport="700x1260" 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>
@@ -14,11 +14,17 @@ const { SegmentedControl, ViewToggle } = window.KeybricksDesignSystem_71231a;
14
14
  const { KBTiers: Tiers } = window;
15
15
  const Case = ({ k, children }) => <div className="case"><span className="k">{k}</span><div className="v">{children}</div></div>;
16
16
  function Sheet() {
17
- const [a, setA] = React.useState("Grid"), [b, setB] = React.useState("Last 30 days"), [c, setC] = React.useState("Table"), [d, setD] = React.useState("Actual"), [e, setE] = React.useState("Month"), [f, setF] = React.useState("Overview"), [v, setV] = React.useState("grid");
17
+ const [n, setN] = React.useState("Submission"), [a, setA] = React.useState("Grid"), [b, setB] = React.useState("Last 30 days"), [c, setC] = React.useState("Table"), [d, setD] = React.useState("Actual"), [e, setE] = React.useState("Month"), [f, setF] = React.useState("Overview"), [v, setV] = React.useState("grid");
18
18
  return <div>
19
19
  <div className="kb-h kb-h-gap">Sizes</div>
20
20
  <Tiers stack style={{gridTemplateColumns:"56px repeat(2,max-content)",columnGap:16}} init={(l)=>l==="en"?"Last 30 days":"近 30 天"} render={(t,l,v,set)=><SegmentedControl options={l==="en"?["Last 7 days","Last 30 days","Quarter"]:["近 7 天","近 30 天","本季度"]} value={v} onChange={set}/>}/>
21
21
 
22
+ <div className="kb-h kb-h-gap">Counts</div>
23
+ <Case k="count on an option"><SegmentedControl options={[{ label: "Submission", count: 3 }, { label: "Review", count: 12 }, { label: "Approved", count: 0 }]} value={n} onChange={setN} /></Case>
24
+ <Case k="showZero · muted 0"><SegmentedControl showZero options={[{ label: "Submission", count: 3 }, { label: "Review", count: 12 }, { label: "Approved", count: 0 }]} value={n} onChange={setN} /></Case>
25
+ <Case k="sizes"><div style={{ display: "flex", flexDirection: "column", gap: 12, alignItems: "flex-start" }}>{["sm", "md", "lg"].map(s => <SegmentedControl key={s} size={s} options={[{ label: "Submission", count: 3 }, { label: "Review", count: 12 }, { label: "Approved" }]} value={n} onChange={setN} />)}</div></Case>
26
+ <div className="note">The badge sits 6px right of the label: <code>--count-h</code> tall (sm 16 · md 18 · lg 20), 6px sides, full radius, 12px tabular figures. The fill is the alpha wash <code>--neutral-alpha-8</code>, so it shows on the white active segment and the #fafafa track alike; the text tells them apart — <code>--text-heading</code> selected, <code>--text-body</code> unselected. A count of 0 or none hides it; <code>showZero</code> (on the control or the option) draws a muted 0. The selected value is still matched by label.</div>
27
+
22
28
  <div className="kb-h kb-h-gap">With icons</div>
23
29
  <Case k="icon + label"><SegmentedControl options={[{ label: "List", icon: "list" }, { label: "Grid", icon: "layout-grid" }]} value={a} onChange={setA} /></Case>
24
30
  <Case k="icon only · aria label"><SegmentedControl options={[{ icon: "table", label: "Table" }, { icon: "chart-column", label: "Chart" }, { icon: "code", label: "JSON" }]} iconOnly value={c} onChange={setC} /></Case>
@@ -36,5 +42,5 @@ function Sheet() {
36
42
  </div>;
37
43
  }
38
44
  ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
39
- if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"SegmentedControl.jsx","path":"../tabs/SegmentedControl.jsx"},{"name":"ViewToggle.jsx","path":"../tabs/ViewToggle.jsx"}]); }, 0);
45
+ if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"SegmentedControl.jsx","path":"../tabs/SegmentedControl.jsx"},{"name":"Tabs.jsx (CountBadge)","path":"../tabs/Tabs.jsx","vue":"../tabs/CountBadge.vue","vueName":"CountBadge.vue"},{"name":"ViewToggle.jsx","path":"../tabs/ViewToggle.jsx"}]); }, 0);
40
46
  </script></body></html>