@mtdt/observeops-ds-spec 0.1.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.
- package/AGENTS.md +102 -0
- package/README.md +73 -0
- package/components/index.json +1270 -0
- package/components/recipes/README.md +41 -0
- package/components/recipes/recipes.json +922 -0
- package/components/registry/README.md +44 -0
- package/components/registry/_schema.json +47 -0
- package/components/registry/button.json +368 -0
- package/components/registry/checkbox.json +177 -0
- package/components/registry/data-viz-tooltips.json +409 -0
- package/components/registry/date-time-pickers.json +296 -0
- package/components/registry/drawer.json +222 -0
- package/components/registry/dropdown-picker.json +388 -0
- package/components/registry/filters.json +155 -0
- package/components/registry/form-item.json +281 -0
- package/components/registry/input.json +277 -0
- package/components/registry/link.json +186 -0
- package/components/registry/loose-tags.json +196 -0
- package/components/registry/menu.json +145 -0
- package/components/registry/modal.json +265 -0
- package/components/registry/navigation.json +425 -0
- package/components/registry/popover.json +216 -0
- package/components/registry/radio.json +238 -0
- package/components/registry/scheduler.json +188 -0
- package/components/registry/select.json +247 -0
- package/components/registry/severity.json +179 -0
- package/components/registry/switch.json +177 -0
- package/components/registry/table.json +275 -0
- package/components/registry/tabs.json +264 -0
- package/components/registry/tag.json +345 -0
- package/components/registry/tags-list.json +115 -0
- package/components/registry/toolbars.json +240 -0
- package/components/registry/tooltip.json +175 -0
- package/components/specs/README.md +72 -0
- package/components/specs/button.md +230 -0
- package/components/specs/checkbox.md +162 -0
- package/components/specs/data-viz-tooltips.md +93 -0
- package/components/specs/date-time-pickers.md +161 -0
- package/components/specs/drawer.md +162 -0
- package/components/specs/dropdown-picker.md +161 -0
- package/components/specs/filters.md +118 -0
- package/components/specs/form-item.md +130 -0
- package/components/specs/input.md +130 -0
- package/components/specs/link.md +131 -0
- package/components/specs/loose-tags.md +139 -0
- package/components/specs/menu.md +88 -0
- package/components/specs/modal.md +176 -0
- package/components/specs/navigation.md +181 -0
- package/components/specs/popover.md +118 -0
- package/components/specs/radio.md +144 -0
- package/components/specs/scheduler.md +133 -0
- package/components/specs/select.md +118 -0
- package/components/specs/switch.md +124 -0
- package/components/specs/table.md +115 -0
- package/components/specs/tabs.md +136 -0
- package/components/specs/tag.md +196 -0
- package/components/specs/tags-list.md +105 -0
- package/components/specs/toolbars.md +108 -0
- package/components/specs/tooltip.md +112 -0
- package/foundation/README.md +39 -0
- package/foundation/layout-shells.md +67 -0
- package/foundation/page-templates.md +69 -0
- package/foundation/panel-behaviours.md +61 -0
- package/foundation/screen-regions.md +62 -0
- package/index.js +75 -0
- package/layout/grid.json +34 -0
- package/layout/layouts.json +310 -0
- package/llms.txt +60 -0
- package/package.json +42 -0
- package/spec.manifest.json +407 -0
- package/tokens/README.md +125 -0
- package/tokens/component.json +34 -0
- package/tokens/kit-accents.json +14 -0
- package/tokens/primitive.json +130 -0
- package/tokens/purpose-map.json +67 -0
- package/tokens/semantic.dark.json +90 -0
- package/tokens/semantic.light.json +90 -0
- package/tokens/structural.json +35 -0
- package/tokens/variables.json +2018 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Select (`MSelect`) — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Atom (form control) |
|
|
6
|
+
| **Maturity** | ⚠️ **Low-use** (2×) — the product's standard select is `FlotoDropdownPicker` |
|
|
7
|
+
| **Source** | `@motadata/ui` → `ui/components/Select/Select.vue` (wraps Ant `a-select`) |
|
|
8
|
+
| **Storybook** | `Atoms/Select` (Examples · Usage · Accessibility · Changelog) |
|
|
9
|
+
| **Registry** | [`../registry/select.json`](../registry/select.json) |
|
|
10
|
+
| **Family** | select cluster — `FlotoDropdownPicker` (the real select) · `LooseTags` · `MTreeSelect` · this |
|
|
11
|
+
| **Figma** | N/A (low-use; build `DropdownPicker` instead) |
|
|
12
|
+
|
|
13
|
+
## Usage (product analytics)
|
|
14
|
+
|
|
15
|
+
- **`<MSelect>` used 2×** — both `mode="tags"` (in `LooseTags` and `object-tag-picker`).
|
|
16
|
+
- **`FlotoDropdownPicker` used 510×** — the de-facto select. **Use that**, not MSelect.
|
|
17
|
+
- `MTreeSelect` 0× · `MSelectOption`/`OptGroup` 0× direct.
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
The kit's raw **Ant `a-select`** wrapper. Full-featured (modes, search, clear, sizes,
|
|
22
|
+
loading), but the product standardized on `FlotoDropdownPicker` instead — so MSelect survives
|
|
23
|
+
only as the low-level primitive behind a couple of tag inputs. Documented for completeness;
|
|
24
|
+
**reach for `FlotoDropdownPicker`** for an actual select. v-model is `value` + `change`.
|
|
25
|
+
|
|
26
|
+
## Options / API
|
|
27
|
+
|
|
28
|
+
- **`mode`** — `default` (single) · `multiple` (chips) · `tags` (free-type to create) · combobox.
|
|
29
|
+
- **`options`** — `{ value, text }` (label resolves `text||label||title||name`); or slot options.
|
|
30
|
+
- **`showSearch`** (default false) · **`allowClear`** (default **true**) · **`size`**
|
|
31
|
+
(small/default/large) · **`loading`** (chevron → spinner) · `disabled` · `maxTagCount` ·
|
|
32
|
+
`labelInValue` · `filterOption` (default case-insensitive on label) · `tokenSeparators`.
|
|
33
|
+
- **Slots:** default/`option` · `suffixIcon` · `clearIcon` · `removeIcon` ·
|
|
34
|
+
`menuItemSelectedIcon` · `notFoundContent` (→ `MNoData`) · `dropdownRender`.
|
|
35
|
+
- **Emits:** `change` (model) · `search` · `select` · `deselect` · `blur` · `focus` · more.
|
|
36
|
+
|
|
37
|
+
## Behaviors
|
|
38
|
+
|
|
39
|
+
- **Single/multiple/tags** like Ant. **Multiple/tags** render removable chips in the field.
|
|
40
|
+
- **Search** (`show-search`) filters options on the label; empty → `MNoData`.
|
|
41
|
+
- **Loading** swaps the chevron for a spinner.
|
|
42
|
+
|
|
43
|
+
## Accessibility
|
|
44
|
+
|
|
45
|
+
- Built on Ant `a-select` → it **IS a real combobox** (`role`, `aria-expanded`,
|
|
46
|
+
`aria-activedescendant`, listbox/options, keyboard open + type-ahead). **Notably, this
|
|
47
|
+
low-use component is *more* accessible than the 510×-used `FlotoDropdownPicker`** (which has
|
|
48
|
+
no combobox semantics — [SF-003](../../findings/SF-003-combobox-a11y.md)). A good reference
|
|
49
|
+
for what fixing SF-003 should achieve.
|
|
50
|
+
- ⚠️ No visible focus ring system-wide ([SF-001](../../findings/SF-001-focus-visible.md)).
|
|
51
|
+
|
|
52
|
+
## Findings & Inconsistencies
|
|
53
|
+
|
|
54
|
+
### F1 — Superseded by `FlotoDropdownPicker` · Medium · Documented
|
|
55
|
+
|
|
56
|
+
The product chose the custom `FlotoDropdownPicker` (510×) over MSelect (2×) — likely for the
|
|
57
|
+
virtualized menu, custom triggers, and inline-add. So MSelect is effectively legacy/low-level.
|
|
58
|
+
**Solution:** use `FlotoDropdownPicker` for selects (and `LooseTags` for tag inputs); keep
|
|
59
|
+
MSelect only as the Ant primitive. Don't expand its usage.
|
|
60
|
+
|
|
61
|
+
### F2 — The accessible select is the unused one · Medium · Open *(a11y, ironic)*
|
|
62
|
+
|
|
63
|
+
MSelect (Ant) has proper combobox a11y; the widely-used `FlotoDropdownPicker` does not
|
|
64
|
+
([SF-003](../../findings/SF-003-combobox-a11y.md)). **Solution:** when fixing SF-003, mirror
|
|
65
|
+
Ant `a-select`'s ARIA — or, longer term, consider consolidating onto an accessible base.
|
|
66
|
+
|
|
67
|
+
### F3 — No visible focus ring · High · Open *(a11y)* → [SF-001](../../findings/SF-001-focus-visible.md)
|
|
68
|
+
|
|
69
|
+
System-wide.
|
|
70
|
+
|
|
71
|
+
### F4 — Clear "×" is invisible (icon prefix mismatch) · Low · Open
|
|
72
|
+
|
|
73
|
+
`Select.vue` hardcodes the clear icon as **`<MIcon name="times-circle" type="fas">`** (Font
|
|
74
|
+
Awesome **solid**), but the product registers **only `fal` (light)** icons (639 `fal`, 0 `fas`
|
|
75
|
+
in `icons.js`). So `fas times-circle` isn't in the library and the clear control renders
|
|
76
|
+
**empty** (`<i class="ant-select-clear-icon"><!----></i>`, no svg) — the **clear × is blank**.
|
|
77
|
+
The chip **remove** × works (it uses `name="times"`, default `fal`). (`type="fas"` is used 3×:
|
|
78
|
+
`Select.vue`, `TreeSelect.vue`, `navbar.vue` — all latently broken.) **Solution:** drop
|
|
79
|
+
`type="fas"` (use the default `fal`), or register the `fas` solid set.
|
|
80
|
+
|
|
81
|
+
### F5 — Clear "×" overlaps the dropdown chevron · Low · Open
|
|
82
|
+
|
|
83
|
+
When `allow-clear` shows the clear control, it sits **on top of** the custom `suffixIcon`
|
|
84
|
+
chevron (both absolutely positioned at the same right edge). Ant's clear masks the *default*
|
|
85
|
+
arrow with a white box, but MSelect's **custom chevron `MIcon` is wider** than that box, so the
|
|
86
|
+
chevron's left edge **pokes out beside the ×** (measured: chevron `left 123`, clear box
|
|
87
|
+
`left 129` — single; same in multiple). Result: the close icon appears *over/beside* the
|
|
88
|
+
dropdown icon. **Solution:** hide `.ant-select-arrow` while the clear is shown, or size the
|
|
89
|
+
clear mask to the custom chevron. **Net (F4+F5): MSelect's clear-all affordance is broken** —
|
|
90
|
+
low impact (MSelect is 2×, both `mode="tags"`, never with `allow-clear`). The catalog stories
|
|
91
|
+
therefore omit `allow-clear` and show removal via the working chip ×; for a clearable select
|
|
92
|
+
use **`FlotoDropdownPicker`**.
|
|
93
|
+
|
|
94
|
+
## Do / Don't
|
|
95
|
+
|
|
96
|
+
### Do
|
|
97
|
+
|
|
98
|
+
- For a real select, use **`FlotoDropdownPicker`** (single/multi/search/virtualized/custom trigger).
|
|
99
|
+
- For a free-form tag input, use **`LooseTags`**.
|
|
100
|
+
- Use MSelect only as the low-level Ant primitive when something specifically needs raw `a-select`.
|
|
101
|
+
|
|
102
|
+
### Don't
|
|
103
|
+
|
|
104
|
+
- Don't introduce new `MSelect` usages as "the select" — that's `FlotoDropdownPicker`.
|
|
105
|
+
- Don't reach for `mode="tags"` here — use `LooseTags`.
|
|
106
|
+
|
|
107
|
+
## Related
|
|
108
|
+
|
|
109
|
+
`FlotoDropdownPicker` (the product select) · `LooseTags` (tag input) · `MTreeSelect` (tree) ·
|
|
110
|
+
`MSelectOption`/`MSelectOptGroup` · `FlotoFormItem` (wrap it for label + validation).
|
|
111
|
+
|
|
112
|
+
## Changelog
|
|
113
|
+
|
|
114
|
+
- **2026-06-07** — Added (badged **low-use**, decision-grade Usage). Deep-dive of `Select.vue`:
|
|
115
|
+
Ant `a-select` wrapper — modes (default/multiple/tags), `show-search`, `allow-clear` (default
|
|
116
|
+
true), sizes, loading, rich slots (`notFoundContent` → `MNoData`); v-model `value`/`change`.
|
|
117
|
+
Verified single/multiple/tags render. Findings F1 (superseded by DropdownPicker, 2× vs 510×),
|
|
118
|
+
**F2** (ironically the *accessible* combobox vs SF-003), F3 (focus → SF-001).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Switch (`MSwitch`) — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Atom |
|
|
6
|
+
| **Maturity** | 🟢 Stable (core) — open a11y findings (F3, F4) |
|
|
7
|
+
| **Source** | `@motadata/ui` kit → `ui/components/Switch/Switch.vue` (wraps Ant `a-switch`). Not overridden; not excluded. |
|
|
8
|
+
| **Storybook** | `Atoms/Switch` (Examples · Usage · Accessibility · Changelog) |
|
|
9
|
+
| **Registry** | [`../registry/switch.json`](../registry/switch.json) |
|
|
10
|
+
| **Figma** | TODO |
|
|
11
|
+
|
|
12
|
+
## Usage (product analytics)
|
|
13
|
+
|
|
14
|
+
- **`<MSwitch>` used 136× across 88 files.**
|
|
15
|
+
- `size="small"` ~76 (approx, shared prop). checked/unchecked **label slots: 0 files** (unused).
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
An **instant** on/off toggle for a setting (applies immediately — no Save). Wraps Ant
|
|
20
|
+
`a-switch`; **controlled** (bind `v-model` / `:checked`).
|
|
21
|
+
|
|
22
|
+
## Anatomy
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
off: ◖○────────◗ gray knob, white track
|
|
26
|
+
on: ◖────────●◗ green knob, white track ← track color unchanged; knob signals state
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Track · knob (gray→green) · optional `#checked` / `#unchecked` label slots (unused).
|
|
30
|
+
|
|
31
|
+
## Options / States
|
|
32
|
+
|
|
33
|
+
- **Sizes:** `default` (22×44px) · `small`.
|
|
34
|
+
- **States:** off · on · disabled · loading (controlled via `:checked`/`v-model`).
|
|
35
|
+
|
|
36
|
+
**Context re-check (2026-06-07):** swept all switch styling (kit override, `form.less`,
|
|
37
|
+
`table.less`, `input.less`) for hidden/context variants like Radio's segmented set. Result:
|
|
38
|
+
**none** — no consumer `*-switch` classes exist, and the table/form rules only set the track
|
|
39
|
+
token (`--switch-bg`) + alignment. The track stays static (`--switch-bg`/white) in both states;
|
|
40
|
+
only the knob colors green (verified: track `#fff`, knob `#14b053`) — this is product-accurate
|
|
41
|
+
(F2). The switch surface is complete (no segmented/variant explosion).
|
|
42
|
+
|
|
43
|
+
## Behaviors
|
|
44
|
+
|
|
45
|
+
- **Controlled:** always reflects `:checked`; toggling emits `change(value)`.
|
|
46
|
+
- **Loading:** `:loading` shows a spinner in the knob during async persistence.
|
|
47
|
+
- **Track is static color:** only the knob changes (gray off → green on) + slides position.
|
|
48
|
+
|
|
49
|
+
## Content & writing
|
|
50
|
+
|
|
51
|
+
Pair with a label naming what turns on ("Enable notifications"). On/off text slots exist
|
|
52
|
+
but the product doesn't use them.
|
|
53
|
+
|
|
54
|
+
## Accessibility
|
|
55
|
+
|
|
56
|
+
- **Semantics:** `role="switch"`; Space/Enter toggle. ✅
|
|
57
|
+
- **State:** `aria-checked="true"` when on ✅; **missing when off** (F4).
|
|
58
|
+
- **Target size:** ~22px tall — below the 44–48px touch target (F5).
|
|
59
|
+
- **Focus:** no visible focus ring (F3).
|
|
60
|
+
- **Theming:** track/border theme in dark (`#fff→#172336`); knob colors (`#a5bad0` off,
|
|
61
|
+
`#14b053` on) are static but read acceptably in both themes.
|
|
62
|
+
|
|
63
|
+
## Props / API
|
|
64
|
+
|
|
65
|
+
`checked` (v-model) · `size` (default/small) · `disabled` · `loading` · `defaultChecked`
|
|
66
|
+
(**no-op — F1**) · `autoFocus`. Slots `checked` / `unchecked`. Emits `change`.
|
|
67
|
+
Machine spec: [`../registry/switch.json`](../registry/switch.json).
|
|
68
|
+
|
|
69
|
+
## Design tokens used
|
|
70
|
+
|
|
71
|
+
Kit LESS vars (static): `@white` (track) · `@neutral-light` (off knob/border) ·
|
|
72
|
+
`@secondary-green` (on knob). Track/border also pick up CSS-var theming in dark.
|
|
73
|
+
|
|
74
|
+
## Findings & Inconsistencies
|
|
75
|
+
|
|
76
|
+
### F1 — `defaultChecked` is a no-op · Medium · Open
|
|
77
|
+
|
|
78
|
+
`MSwitch` always passes `:checked` (default `false`) to `a-switch`, which overrides
|
|
79
|
+
`:defaultChecked` → the switch never starts on via `defaultChecked`. **Solution:** use
|
|
80
|
+
`:checked`/`v-model`; or in the kit, don't bind `:checked` when uncontrolled. Document
|
|
81
|
+
that `defaultChecked` does nothing.
|
|
82
|
+
|
|
83
|
+
### F2 — Track color doesn't change; state is knob-color-only · Medium · Open
|
|
84
|
+
|
|
85
|
+
Off and on share the same (white/dark) track; only the knob color (gray→green) + position
|
|
86
|
+
differ. Subtler than a track-fill switch and leans on color. **Solution:** confirm intent;
|
|
87
|
+
consider a checked **track** fill (e.g. `--severity-clear` tint) for a stronger signal.
|
|
88
|
+
|
|
89
|
+
### F3 — No visible focus indicator · High · Open *(a11y)* → see SF-001
|
|
90
|
+
|
|
91
|
+
`outline: none`, no focus shadow (WCAG 2.4.7). **System-wide** — tracked as
|
|
92
|
+
[SF-001](../../findings/SF-001-focus-visible.md); fix once globally.
|
|
93
|
+
|
|
94
|
+
### F4 — `aria-checked` absent in the OFF state · Medium · Open *(a11y)*
|
|
95
|
+
|
|
96
|
+
On exposes `aria-checked="true"`; off is null → AT may not announce "off". **Solution:**
|
|
97
|
+
ensure `aria-checked="false"` is set when unchecked.
|
|
98
|
+
|
|
99
|
+
### F5 — 22px height below touch target · Low · Open *(a11y)*
|
|
100
|
+
|
|
101
|
+
**Solution:** ensure ≥44px hit area (padding) on touch surfaces.
|
|
102
|
+
|
|
103
|
+
## Do / Don't
|
|
104
|
+
|
|
105
|
+
### Do
|
|
106
|
+
|
|
107
|
+
- Use for **immediate** on/off settings; show `:loading` while persisting.
|
|
108
|
+
- Control via `v-model` / `:checked`.
|
|
109
|
+
|
|
110
|
+
### Don't
|
|
111
|
+
|
|
112
|
+
- Don't use a switch in a form that needs explicit Save (use `MCheckbox`).
|
|
113
|
+
- Don't rely on `:defaultChecked` (F1).
|
|
114
|
+
|
|
115
|
+
## Related components
|
|
116
|
+
|
|
117
|
+
`MCheckbox` (needs Save) · `MRadio` (mutually exclusive).
|
|
118
|
+
|
|
119
|
+
## Changelog
|
|
120
|
+
|
|
121
|
+
- **2026-06-05** — Added (Playground · States · Sizes).
|
|
122
|
+
- **2026-06-06** — Full deep-dive: examples fixed to `:checked` (F1); on/off rendering
|
|
123
|
+
measured (white track + gray→green knob, light/dark). Enhanced standard + Option B pages;
|
|
124
|
+
a11y findings F3 (focus ring) + F4 (off-state aria-checked) + F5 (target size).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Table / Grid (`MGrid`) — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Organism |
|
|
6
|
+
| **Maturity** | 🟢 Stable |
|
|
7
|
+
| **Source** | `src/components/crud/_base-grid.vue` (**MGrid**) — wraps the **Kendo Vue Grid** (`@progress/kendo-vue-grid`) + `gridWorker` / `arrayWorker`. Fetch+paginate wrapper: **`FlotoPaginatedCrud`**. |
|
|
8
|
+
| **Storybook** | Organisms/Table |
|
|
9
|
+
| **Registry** | [`registry/table.json`](../registry/table.json) |
|
|
10
|
+
| **Family** | [Data Table](../family-map.md) |
|
|
11
|
+
|
|
12
|
+
## Usage analytics
|
|
13
|
+
|
|
14
|
+
- **MGrid 75×** across 69 files · **FlotoPaginatedCrud 76×** (the usual driver). Top modules:
|
|
15
|
+
settings, apm, ncm, slo, alert — i.e. **every list / inventory / drill-down view**.
|
|
16
|
+
- States in the wild (from the sweep): **expandable** 8× · **selectable** 7× · `hide-selection-info`
|
|
17
|
+
5× · `disable-pre-selected-item` 4× · grouping (`default-group`). Classes: `hide-expand-column`
|
|
18
|
+
(6×), `hide-header`, `hide-grouping`, `tabular-content-grid`, `service-grid`, `rum-grid`.
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
The product's **primary data table**. `MGrid` wraps the Kendo grid and offloads sort/filter/group/
|
|
23
|
+
page to web workers; **`FlotoPaginatedCrud`** wraps `MGrid` to own fetch + paging + search + filters
|
|
24
|
+
for list pages. Features: resizable / reorderable / sortable columns, **client + server paging**,
|
|
25
|
+
**grouping**, **selection** (→ bulk-action bar), **expandable detail rows**, and **per-column cell
|
|
26
|
+
slots**. For a few static key/values, use a list/cards instead.
|
|
27
|
+
|
|
28
|
+
## Anatomy
|
|
29
|
+
|
|
30
|
+
- **Header** — column titles on `--grid-header-bg`; sortable, resizable, reorderable. Optional
|
|
31
|
+
select-all checkbox.
|
|
32
|
+
- **Rows** — `--border-color` bottom divider; **hover** highlight; **selected** = `--neutral-lightest`
|
|
33
|
+
+ a `--primary` left accent; optional **detail row** (expand); optional **group header** rows.
|
|
34
|
+
- **Footer** — pager (range + page controls) when `paging`.
|
|
35
|
+
- **Selection tag** — "N items selected" (`MTag`) above the grid unless `hide-selection-info`.
|
|
36
|
+
|
|
37
|
+
## Options (key props)
|
|
38
|
+
|
|
39
|
+
| Prop | Default | Notes |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `columns` | — | column defs (field/title/width/cell/sortable) |
|
|
42
|
+
| `data` | — | rows (client mode) |
|
|
43
|
+
| `selectable` | `false` | checkbox column + selection |
|
|
44
|
+
| `expandable` | `false` | chevron + `detailRow` slot |
|
|
45
|
+
| `paging` / `default-page-size` | `false` / `50` | client paging |
|
|
46
|
+
| `external-take` / `external-skip` / `total-count` | — | **server paging** |
|
|
47
|
+
| `default-sort` / `default-group` | — | initial sort / grouping |
|
|
48
|
+
| `filters` / `search-term` / `use-search-term-loading` | — | filtering / search (+ spinner) |
|
|
49
|
+
| `max-allowed-selection` / `pre-selected-items` / `selection-disabled-items` / `disable-pre-selected-item` | — | selection control |
|
|
50
|
+
| `hide-selection-info` | `false` | hide the "N selected" tag |
|
|
51
|
+
| `row-height` | — | virtualization (non-paging) |
|
|
52
|
+
|
|
53
|
+
## Behaviors
|
|
54
|
+
|
|
55
|
+
- **Workers:** sort/filter/group/page run in `gridWorker`/`arrayWorker`; the grid shows a
|
|
56
|
+
`FlotoContentLoader` while `processingData`.
|
|
57
|
+
- **Server vs client:** `external-*` + `total-count` + `@data-state-change` = server-side; otherwise
|
|
58
|
+
the grid pages/sorts in memory.
|
|
59
|
+
- **Columns:** resizable + reorderable; `@column-change` persists layout. Some columns are
|
|
60
|
+
sort-disabled (`SORT_DISABLED_COLUMNS`).
|
|
61
|
+
|
|
62
|
+
## Content & writing
|
|
63
|
+
|
|
64
|
+
- Column titles short and consistent; empty → **"No records found"**.
|
|
65
|
+
|
|
66
|
+
## Accessibility
|
|
67
|
+
|
|
68
|
+
- Verify Kendo grid + **custom cell slots** keep table roles (`row`/`columnheader`/`gridcell`),
|
|
69
|
+
**`aria-sort`** on sortable headers, **`aria-selected`** on selected rows, an accessible
|
|
70
|
+
select-all, and **focus rings** on cell controls (SF-001). Announce empty/loading via a live
|
|
71
|
+
region. See the Accessibility page (**F2**).
|
|
72
|
+
|
|
73
|
+
## Props / API
|
|
74
|
+
|
|
75
|
+
See the table above and [`registry/table.json`](../registry/table.json).
|
|
76
|
+
|
|
77
|
+
## Design tokens used
|
|
78
|
+
|
|
79
|
+
`--grid-header-bg` · `--border-color` · `--neutral-lightest` (selected) · `--neutral-lighter`
|
|
80
|
+
(group header) · `--primary` (selected accent) · `--neutral-dark` (header text) ·
|
|
81
|
+
`--page-background-color`.
|
|
82
|
+
|
|
83
|
+
## Findings & Inconsistencies
|
|
84
|
+
|
|
85
|
+
| # | Severity | Status | Finding |
|
|
86
|
+
| --- | --- | --- | --- |
|
|
87
|
+
| F1 | Low | Open | Heavy: Kendo grid + 2 web workers — not trivially renderable outside the app (Storybook shows **reproductions**, not the live grid). |
|
|
88
|
+
| F2 | Medium (a11y) | Open | Verify table roles / `aria-sort` / `aria-selected` / focus survive the Kendo grid + custom cell slots. |
|
|
89
|
+
|
|
90
|
+
## Recommended solutions
|
|
91
|
+
|
|
92
|
+
- **F1:** keep the catalog as faithful reproductions; if a live grid is wanted later, sandbox the
|
|
93
|
+
Kendo grid with mocked workers in a dedicated story.
|
|
94
|
+
- **F2:** audit the rendered grid for ARIA (sort/selected/roles) and adopt the SF-001 focus ring on
|
|
95
|
+
cell controls.
|
|
96
|
+
|
|
97
|
+
## Do / Don't
|
|
98
|
+
|
|
99
|
+
- **Do** use `FlotoPaginatedCrud` for fetched lists; server-page large data; row actions in a kebab;
|
|
100
|
+
multi-select via the bulk bar; keep the header in empty/loading.
|
|
101
|
+
- **Don't** use a grid for a few static key/values; don't client-load huge datasets; don't
|
|
102
|
+
hand-roll selection/sorting/paging.
|
|
103
|
+
|
|
104
|
+
## Related components
|
|
105
|
+
|
|
106
|
+
`FlotoPaginatedCrud` · `FlotoGridActions` (row kebab) · bulk-action bar · `MStatusTag` / `MTag`
|
|
107
|
+
(cells) · `MCheckbox` (selection) · `FlotoDropdownPicker` (filters).
|
|
108
|
+
|
|
109
|
+
## Changelog
|
|
110
|
+
|
|
111
|
+
- **2026-06-12** — Added (decision-grade Usage). `/component-sweep` on `MGrid` (75×) +
|
|
112
|
+
`FlotoPaginatedCrud` (76×): Kendo grid + workers; states selectable/expandable/grouping/paging;
|
|
113
|
+
per-column cell slots. 8 reference-reproduction stories (Basic · Selectable · Expandable · Cell
|
|
114
|
+
types · Grouping · Empty · Loading · Pagination) on the real `table.less` chrome; verified render
|
|
115
|
+
+ the selected-row accent. Findings F1 (heavy/reproductions), F2 (a11y).
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Tabs (`MTab` / `MTabPane`) — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Molecule |
|
|
6
|
+
| **Maturity** | 🟢 Stable |
|
|
7
|
+
| **Source** | `ui/components/Tabs/Tab.vue` (`MTab` — wraps Ant `a-tabs`) + `ui/components/Tabs/TabPane.vue` (`MTabPane`). Persisted state: `src/components/_base-persisted-tab.vue` (`MPersistedTab` — renderless). |
|
|
8
|
+
| **Storybook** | Molecules/Tabs |
|
|
9
|
+
| **Registry** | [`registry/tabs.json`](../registry/tabs.json) |
|
|
10
|
+
| **Family** | [Navigation / Tabs](../family-map.md) |
|
|
11
|
+
|
|
12
|
+
## Usage analytics
|
|
13
|
+
|
|
14
|
+
- **86×** across **70** files. Top areas: `settings` (20), shared `components` (17), `rum` (7),
|
|
15
|
+
`alert` (5), `log` (4), `apm` (3).
|
|
16
|
+
- **Always line / top / default.** No real `<MTab>` passes `type=`, `position=`, or `size=` — so
|
|
17
|
+
despite Ant supporting **card / editable-card** types, **vertical** position, **size** variants,
|
|
18
|
+
and a **`tabBarExtraContent`** slot, the product uses **none** of them.
|
|
19
|
+
- **Variant classes (the real API):** **`no-border`** (21×, dominant) · **`sticky-tab`** (8×) ·
|
|
20
|
+
`topology-hierarchy-tab` (3×, icon-only) · `metric-picker-tabs` (1×) · `grey-tab` (defined, ~0) ·
|
|
21
|
+
`flex-tabs` (content flex helper).
|
|
22
|
+
- **Label patterns:** **count appended to the label string** (`Alerts (${count})`,
|
|
23
|
+
`${tab.title} (${getCounts(tab)})`) — *not* a separate badge; **leading icon** via the pane's
|
|
24
|
+
`tab` slot; **dynamic** `v-for` over a tabs config array.
|
|
25
|
+
|
|
26
|
+
## Overview
|
|
27
|
+
|
|
28
|
+
In-page **tabbed navigation** that switches between **sibling views of one context** (Overview /
|
|
29
|
+
Performance / Logs of a monitor; Attributes / Metric / Style of a widget editor). `MTab` wraps Ant
|
|
30
|
+
`a-tabs` and applies its `variant` prop **as a CSS class**; panes are `MTabPane`. For page-to-page
|
|
31
|
+
navigation use the router/menu, not tabs.
|
|
32
|
+
|
|
33
|
+
## Anatomy
|
|
34
|
+
|
|
35
|
+
- **Tab bar** — the strip of tab triggers; bottom border `--border-color` (removed by `no-border`).
|
|
36
|
+
- **Tab trigger** — active = `--primary` text + a `--primary` underline/ink bar; inactive =
|
|
37
|
+
`--tabs-text-color`; disabled = `--neutral-light`.
|
|
38
|
+
- **Tab pane** (`MTabPane`) — the content for the active key; label via the `tab` prop or `tab` slot.
|
|
39
|
+
|
|
40
|
+
## Options (props)
|
|
41
|
+
|
|
42
|
+
### `MTab`
|
|
43
|
+
|
|
44
|
+
| Prop | Default | Notes |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `value` | — | active key; `v-model` via `@change` (41× bind change) |
|
|
47
|
+
| `variant` | `''` | applied as a **class** on `a-tabs` — e.g. `no-border` |
|
|
48
|
+
| `defaultActive` | — | `defaultActiveKey` when uncontrolled |
|
|
49
|
+
| `animated` | `false` | slide animation between panes |
|
|
50
|
+
| `size` | `default` | **unused** in product |
|
|
51
|
+
| `position` | `top` | `tabPosition`; **unused** beyond `top` (no vertical tabs) |
|
|
52
|
+
| `type` | `line` | **unused** beyond `line` (no card/editable-card) |
|
|
53
|
+
| `tabBarGutter` / `tabBarStyle` | — | spacing / inline style of the bar |
|
|
54
|
+
|
|
55
|
+
### `MTabPane`
|
|
56
|
+
|
|
57
|
+
| Prop | Default | Notes |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `tab` | — | the label **string** (use the `tab` **slot** for icon/count markup) |
|
|
60
|
+
| `key` | — | the pane key — matches `MTab` `value` |
|
|
61
|
+
| `forceRender` | `false` | render content even when the pane is inactive |
|
|
62
|
+
|
|
63
|
+
### `MPersistedTab` (renderless)
|
|
64
|
+
|
|
65
|
+
| Prop | Default | Notes |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `moduleKey` | **required** | localStorage key → `${moduleKey}-tab` |
|
|
68
|
+
| `useLocalStorageTab` | `true` | persist the active tab |
|
|
69
|
+
| `defaultValue` / `value` | — | initial tab when nothing is stored |
|
|
70
|
+
|
|
71
|
+
Exposes a **scoped slot** `{ tab, setTab }` — wire to `<MTab :value="tab" @change="setTab">`.
|
|
72
|
+
|
|
73
|
+
## Behaviors
|
|
74
|
+
|
|
75
|
+
- **Controlled** via `value` + `@change` (the dominant pattern) or **uncontrolled** via
|
|
76
|
+
`defaultActive`.
|
|
77
|
+
- **`no-border`** drops the bar's bottom rule (tabs on a card). **`sticky-tab`** pins the bar to the
|
|
78
|
+
top of a scroll container (`position:sticky; top:0; background:var(--page-background-color)`).
|
|
79
|
+
- **`MPersistedTab`** remembers the active tab across reloads/navigation in localStorage; its sibling
|
|
80
|
+
**`MPersistedColumns`** does the same for grid columns.
|
|
81
|
+
|
|
82
|
+
## Content & writing
|
|
83
|
+
|
|
84
|
+
- Short noun labels (Overview, Performance, Logs). Append a count as `Label (N)` when it helps the
|
|
85
|
+
user gauge volume. Don't sentence-case or punctuate labels.
|
|
86
|
+
|
|
87
|
+
## Accessibility
|
|
88
|
+
|
|
89
|
+
- **Provided by Ant `a-tabs`:** `role="tablist"` / `role="tab"` / `role="tabpanel"`,
|
|
90
|
+
**`aria-selected`** on the active tab, and **Left/Right arrow-key** navigation between tabs.
|
|
91
|
+
- **Verify:** keyboard **focus visibility** on the tab triggers (catalog-wide **SF-001**
|
|
92
|
+
`:focus-visible` ring); and that **icon-only** tabs carry a meaningful accessible name.
|
|
93
|
+
|
|
94
|
+
## Design tokens used
|
|
95
|
+
|
|
96
|
+
`--primary` (active text + underline + ink bar) · `--tabs-text-color` (inactive) · `--border-color`
|
|
97
|
+
(bar) · `--page-background-color` (sticky bg) · `--neutral-light` (disabled / prev-next) ·
|
|
98
|
+
`--neutral-lightest` (`grey-tab` bar).
|
|
99
|
+
|
|
100
|
+
## Findings & Inconsistencies
|
|
101
|
+
|
|
102
|
+
| # | Severity | Status | Finding |
|
|
103
|
+
| --- | --- | --- | --- |
|
|
104
|
+
| F1 | Low (a11y) | Open | Catalog-wide focus-ring removal (**SF-001**) may suppress the tab trigger's `:focus-visible` outline — verify keyboard focus is visible. |
|
|
105
|
+
| N1 | Info | — | Ant offers card/editable-card/vertical/size/`tabBarExtraContent`; the product uses none. Treat any of these as a **new pattern** requiring a deliberate decision, not a default. |
|
|
106
|
+
|
|
107
|
+
## Recommended solutions
|
|
108
|
+
|
|
109
|
+
- **F1:** adopt the shared `:focus-visible` ring (SF-001) so tab triggers show a visible focus state.
|
|
110
|
+
- **N1:** keep the line/top/default constraint unless a product need justifies a new tab style; if
|
|
111
|
+
introduced, add it to this spec + a story so it's a documented variant, not an ad-hoc divergence.
|
|
112
|
+
|
|
113
|
+
## Do / Don't
|
|
114
|
+
|
|
115
|
+
- **Do** use tabs for sibling views of one context; use `no-border` on cards; `sticky-tab` on long
|
|
116
|
+
panes; append `(N)` counts; build data-driven sets with `v-for`; use `MPersistedTab` for
|
|
117
|
+
remembered tabs.
|
|
118
|
+
- **Don't** use tabs for page navigation; don't introduce card/vertical/closable tabs without a
|
|
119
|
+
decision; don't build a separate count badge; don't depend on `tabBarExtraContent` to match the
|
|
120
|
+
product.
|
|
121
|
+
|
|
122
|
+
## Related components
|
|
123
|
+
|
|
124
|
+
`FlotoDropdownPicker` (select) · `MGrid` / Table (tabbed detail panes often sit above a grid) ·
|
|
125
|
+
`MRadioGroup` `as-button` (a *segmented* control — a different "pick one" pattern, not view tabs).
|
|
126
|
+
|
|
127
|
+
## Changelog
|
|
128
|
+
|
|
129
|
+
- **2026-06-15** — Added (decision-grade). Deep-dive of `MTab`/`MTabPane` (86× / 70 files) + the
|
|
130
|
+
renderless `MPersistedTab`. Established the **line/top/default-only** fidelity rule (no real usage
|
|
131
|
+
passes type/position/size) and the class-based variant API (`no-border` 21×, `sticky-tab` 8×).
|
|
132
|
+
Stories built with the **real** `MTab`/`MTabPane`: Basic · No border · Sticky · With counts ·
|
|
133
|
+
Dynamic · With icons · Persisted. Verified the active indicator renders **`--primary` navy**
|
|
134
|
+
(`rgb(17,28,44)`, text + underline + ink bar), interactivity (click switches panes), and no console
|
|
135
|
+
errors; caught + fixed a blank `map` icon (not in the 543-set → `sitemap`). Finding F1 (focus ring
|
|
136
|
+
→ SF-001).
|