@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.
Files changed (79) hide show
  1. package/AGENTS.md +102 -0
  2. package/README.md +73 -0
  3. package/components/index.json +1270 -0
  4. package/components/recipes/README.md +41 -0
  5. package/components/recipes/recipes.json +922 -0
  6. package/components/registry/README.md +44 -0
  7. package/components/registry/_schema.json +47 -0
  8. package/components/registry/button.json +368 -0
  9. package/components/registry/checkbox.json +177 -0
  10. package/components/registry/data-viz-tooltips.json +409 -0
  11. package/components/registry/date-time-pickers.json +296 -0
  12. package/components/registry/drawer.json +222 -0
  13. package/components/registry/dropdown-picker.json +388 -0
  14. package/components/registry/filters.json +155 -0
  15. package/components/registry/form-item.json +281 -0
  16. package/components/registry/input.json +277 -0
  17. package/components/registry/link.json +186 -0
  18. package/components/registry/loose-tags.json +196 -0
  19. package/components/registry/menu.json +145 -0
  20. package/components/registry/modal.json +265 -0
  21. package/components/registry/navigation.json +425 -0
  22. package/components/registry/popover.json +216 -0
  23. package/components/registry/radio.json +238 -0
  24. package/components/registry/scheduler.json +188 -0
  25. package/components/registry/select.json +247 -0
  26. package/components/registry/severity.json +179 -0
  27. package/components/registry/switch.json +177 -0
  28. package/components/registry/table.json +275 -0
  29. package/components/registry/tabs.json +264 -0
  30. package/components/registry/tag.json +345 -0
  31. package/components/registry/tags-list.json +115 -0
  32. package/components/registry/toolbars.json +240 -0
  33. package/components/registry/tooltip.json +175 -0
  34. package/components/specs/README.md +72 -0
  35. package/components/specs/button.md +230 -0
  36. package/components/specs/checkbox.md +162 -0
  37. package/components/specs/data-viz-tooltips.md +93 -0
  38. package/components/specs/date-time-pickers.md +161 -0
  39. package/components/specs/drawer.md +162 -0
  40. package/components/specs/dropdown-picker.md +161 -0
  41. package/components/specs/filters.md +118 -0
  42. package/components/specs/form-item.md +130 -0
  43. package/components/specs/input.md +130 -0
  44. package/components/specs/link.md +131 -0
  45. package/components/specs/loose-tags.md +139 -0
  46. package/components/specs/menu.md +88 -0
  47. package/components/specs/modal.md +176 -0
  48. package/components/specs/navigation.md +181 -0
  49. package/components/specs/popover.md +118 -0
  50. package/components/specs/radio.md +144 -0
  51. package/components/specs/scheduler.md +133 -0
  52. package/components/specs/select.md +118 -0
  53. package/components/specs/switch.md +124 -0
  54. package/components/specs/table.md +115 -0
  55. package/components/specs/tabs.md +136 -0
  56. package/components/specs/tag.md +196 -0
  57. package/components/specs/tags-list.md +105 -0
  58. package/components/specs/toolbars.md +108 -0
  59. package/components/specs/tooltip.md +112 -0
  60. package/foundation/README.md +39 -0
  61. package/foundation/layout-shells.md +67 -0
  62. package/foundation/page-templates.md +69 -0
  63. package/foundation/panel-behaviours.md +61 -0
  64. package/foundation/screen-regions.md +62 -0
  65. package/index.js +75 -0
  66. package/layout/grid.json +34 -0
  67. package/layout/layouts.json +310 -0
  68. package/llms.txt +60 -0
  69. package/package.json +42 -0
  70. package/spec.manifest.json +407 -0
  71. package/tokens/README.md +125 -0
  72. package/tokens/component.json +34 -0
  73. package/tokens/kit-accents.json +14 -0
  74. package/tokens/primitive.json +130 -0
  75. package/tokens/purpose-map.json +67 -0
  76. package/tokens/semantic.dark.json +90 -0
  77. package/tokens/semantic.light.json +90 -0
  78. package/tokens/structural.json +35 -0
  79. package/tokens/variables.json +2018 -0
@@ -0,0 +1,296 @@
1
+ {
2
+ "name": "date-time-pickers",
3
+ "display": "Date & Time Pickers",
4
+ "tier": "molecule",
5
+ "family": "Date & Time Pickers",
6
+ "status": "stable",
7
+ "summary": "The product's date/time selection family. The hero is TimeRangePicker (42×) — the observability time-range control (relative presets + absolute custom range). MDatePicker (10×, wraps a-date-picker) is the form date-time field, in practice ALWAYS used with show-time. MTimePicker wraps a-time-picker (12-hour) but is rarely used standalone. DateTimePopover is a lighter custom-range-only popover.",
8
+ "variants": [
9
+ {
10
+ "name": "TimeRangePicker",
11
+ "what": "presets + custom range",
12
+ "usage": "42×"
13
+ },
14
+ {
15
+ "name": "presets-only",
16
+ "what": "hide-custom-time-range",
17
+ "usage": "24×"
18
+ },
19
+ {
20
+ "name": "excluded-options",
21
+ "what": "exclude preset keys",
22
+ "usage": "7×"
23
+ },
24
+ {
25
+ "name": "MDatePicker",
26
+ "what": "date-time (show-time)",
27
+ "usage": "10×"
28
+ },
29
+ {
30
+ "name": "MTimePicker",
31
+ "what": "12h time",
32
+ "usage": "ref"
33
+ },
34
+ {
35
+ "name": "TimeRangeSlider",
36
+ "what": "draggable scrubber",
37
+ "usage": "2×"
38
+ },
39
+ {
40
+ "name": "DateTimePopover",
41
+ "what": "custom-range-only popover",
42
+ "usage": "1×"
43
+ }
44
+ ],
45
+ "sizes": [],
46
+ "states": [
47
+ {
48
+ "name": "default",
49
+ "what": "pill + label"
50
+ },
51
+ {
52
+ "name": "empty",
53
+ "what": "'Select Time' placeholder"
54
+ },
55
+ {
56
+ "name": "open",
57
+ "what": "panel/calendar visible"
58
+ },
59
+ {
60
+ "name": "disabled",
61
+ "what": "not interactive"
62
+ }
63
+ ],
64
+ "members": {
65
+ "TimeRangePicker": {
66
+ "source": "src/components/widgets/time-range-picker.vue (name TimeRangePicker)",
67
+ "usage": "42x / 35 files — the dominant date/time control",
68
+ "renders": "REFERENCE REPRODUCTION in Storybook (depends on the Vuex user-preference store + the `datetime` filter + moment, so it can't render live — like the Kendo grid).",
69
+ "presets": [
70
+ "Last 5 Mins (5m)",
71
+ "Last 15 Mins (15m)",
72
+ "Last 30 Mins (30m)",
73
+ "Last 1 Hour (1h)",
74
+ "Last 6 Hours (6h)",
75
+ "Last 12 Hours (12h)",
76
+ "Last 24 Hours (24h)",
77
+ "Last 48 Hours (48h)",
78
+ "Today (today)",
79
+ "Last Day (1d)",
80
+ "Last Week (1w)",
81
+ "Last Month (1mo)",
82
+ "This Week (week)",
83
+ "This Month (month)",
84
+ "Custom ('')"
85
+ ],
86
+ "props": {
87
+ "value": "v-model — object { selectedKey, startDate, endDate, startTime, endTime, dailyRollingData }",
88
+ "hideCustomTimeRange": "bool, 24× — drop the 'Custom' option (presets-only mode); the dominant variant",
89
+ "excludedOptions": "array, 7× — drop specific preset keys (e.g. ['today']); a real, common variant",
90
+ "allowClear": "bool, 7× — show a times-circle × to reset (emits change=undefined)",
91
+ "hideIcon": "bool, 3× — passed by consumers",
92
+ "maxSelectableRangeDays": "number, default 93 (365 for report export) — disables dates beyond the span in the custom calendar (2×)",
93
+ "disabled": "bool, 3×",
94
+ "getPopupContainer": "function, 2×",
95
+ "bordered": "bool, 1× — add a --border-color frame to the trigger",
96
+ "pillStyle": "object, 1× — inline style for the shortcut pill",
97
+ "hideSelectedTime": "bool, 1× — hide the absolute from/to shown beside the trigger",
98
+ "overlayClassName": "string, 1×",
99
+ "onlyLabel": "bool — DECLARED BUT UNUSED (0×); dailyRolling also 0×"
100
+ },
101
+ "emit": "change/v-model -> { selectedKey:'-24h'|'today'|'custom'|…, startDate:<ms>, endDate:<ms>, startTime:'HH:mm:ss', endTime:'HH:mm:ss', dailyRollingData:false }. Custom range validates end > start or blocks the emit.",
102
+ "trigger": "a --timerange-background-color shortcut pill (e.g. '24h' or a computed duration '6d 23h') + separator + the range label ('Last 24 Hours'); empty state shows a calendar-alt icon + 'Select Time'."
103
+ },
104
+ "MDatePicker": {
105
+ "source": "ui/components/Datepicker/Datepicker.vue (wraps a-date-picker)",
106
+ "usage": "10x / 8 files",
107
+ "renders": "REAL kit component (no store deps)",
108
+ "props": {
109
+ "value": "moment | string | number; v-model (model event=change)",
110
+ "showTime": "bool|object, default { use12Hours:true, format:'hh:mm A' } — ALL 10 real usages pass :show-time, so MDatePicker is the date-TIME field",
111
+ "allowClear": "9×",
112
+ "minDate": "7× — earliest selectable date",
113
+ "disabled": "5×",
114
+ "disabledDate": "function — grey out days",
115
+ "format": "display format",
116
+ "placeholder": "default 'Select...'"
117
+ },
118
+ "icon": "calendar suffix icon; slot dateRender for custom day cells"
119
+ },
120
+ "MTimePicker": {
121
+ "source": "ui/components/Datepicker/Timepicker.vue (wraps a-time-picker)",
122
+ "usage": "0× standalone — NOT used as a standalone tag anywhere in src. Time-of-day is entered via MDatePicker show-time or inside the TimeRangePicker custom view.",
123
+ "renders": "REAL kit component (shown for reference)",
124
+ "props": {
125
+ "format": "default 'hh:mm A'",
126
+ "use12Hours": "default true",
127
+ "allowClear": "default true",
128
+ "placeholder": "default 'Select...'"
129
+ },
130
+ "emit": "change(formattedString, moment)",
131
+ "icon": "clock suffix icon"
132
+ },
133
+ "TimePicker (custom)": {
134
+ "source": "src/components/time-picker.vue (name TimePicker)",
135
+ "usage": "0× standalone; used INSIDE TimeRangePicker's custom view (From/To time)",
136
+ "renders": "internal — wraps FlotoDropdownPicker (a DROPDOWN of time options + clock icon), NOT an Ant spinner",
137
+ "props": {
138
+ "value": "Array|Object|String",
139
+ "multiple": "bool",
140
+ "useSeconds": "bool (seconds granularity)",
141
+ "usePopover": "bool",
142
+ "...$attrs": "passed through to FlotoDropdownPicker"
143
+ }
144
+ },
145
+ "TimeRangeSlider": {
146
+ "source": "src/components/widgets/time-range-picker/time-range-slider.vue (name TimeRangeSlider)",
147
+ "usage": "2× — dashboard view + alert correlation drawer",
148
+ "renders": "REFERENCE REPRODUCTION (Ant range Slider styled as a timeline; needs moment/helper/datetime filter)",
149
+ "anatomy": "hidden rail; tick dots (2x14px) with time-mark labels every 10th (HH:mm / DD/MM / MMM by span); two 10x10 square handles; a --primary track for the selected window. min/max auto-expand to ~4x the current duration so dragging widens the window.",
150
+ "emit": "change -> { selectedKey:'custom', startDate, endDate, startTime, endTime } (same shape as TimeRangePicker)"
151
+ },
152
+ "DateTimePopover": {
153
+ "source": "src/components/common/date-time-popover.vue",
154
+ "usage": "1× (SLO correction profile) — a lighter CUSTOM-RANGE-ONLY popover (no presets)",
155
+ "renders": "documented (MPopover + FlotoFormItem + store deps)",
156
+ "emit": "{ startDate, endDate, startTime, endTime }"
157
+ }
158
+ },
159
+ "decisionFlow": [
160
+ "Selecting a TIME WINDOW for charts/dashboards/logs (relative or absolute)? -> TimeRangePicker (the 42× hero).",
161
+ "Want presets only (no absolute calendar)? -> TimeRangePicker :hide-custom-time-range (24×).",
162
+ "A single date or date+time value in a FORM field? -> MDatePicker with :show-time (the product default).",
163
+ "Just a time of day (e.g. a schedule)? -> MTimePicker (12-hour).",
164
+ "A custom absolute from/to with NO presets, as a form field? -> DateTimePopover.",
165
+ "Constrain how far back/forward? -> :max-selectable-range-days (TimeRangePicker) or :min-date / :disabled-date (MDatePicker)."
166
+ ],
167
+ "usageRules": {
168
+ "timeWindow": {
169
+ "useWhen": "pick a chart/dashboard/log time window",
170
+ "how": "<TimeRangePicker v-model>",
171
+ "example": "metric explorer, dashboards, log search"
172
+ },
173
+ "presetsOnly": {
174
+ "useWhen": "absolute ranges shouldn't be allowed",
175
+ "how": ":hide-custom-time-range",
176
+ "example": "widgets where only relative windows make sense"
177
+ },
178
+ "dateTimeField": {
179
+ "useWhen": "a date+time value in a form",
180
+ "how": "<MDatePicker :show-time v-model>",
181
+ "example": "maintenance window start, schedule"
182
+ },
183
+ "timeField": {
184
+ "useWhen": "a time of day",
185
+ "how": "<MTimePicker v-model>",
186
+ "example": "daily run time"
187
+ }
188
+ },
189
+ "tokensUsed": [
190
+ "--timerange-background-color",
191
+ "--timerange-text-color",
192
+ "--dropdown-background",
193
+ "--dropdown-hover-background",
194
+ "--left-menu-text-color-hover",
195
+ "--border-color",
196
+ "--primary",
197
+ "--calendar-selected-day-background-color",
198
+ "--tag-bg",
199
+ "--secondary-red",
200
+ "--neutral-light",
201
+ "--neutral-lightest",
202
+ "--page-text-color"
203
+ ],
204
+ "do": [
205
+ "Use TimeRangePicker for chart/dashboard/log time windows.",
206
+ "Use :hide-custom-time-range when only relative windows are valid.",
207
+ "Use MDatePicker WITH :show-time for date-time form fields (the product norm).",
208
+ "Constrain selection with :max-selectable-range-days / :min-date where it matters."
209
+ ],
210
+ "dont": [
211
+ "Don't hand-roll a relative-range dropdown — use TimeRangePicker.",
212
+ "Don't use a bare date picker for chart windows — that's TimeRangePicker's job.",
213
+ "Don't pass an empty string '' to MTimePicker/MDatePicker value (parses to an Invalid date) — pass null/undefined or a moment.",
214
+ "Don't assume card/inline calendar variants — the product uses the popup pickers only."
215
+ ],
216
+ "knownIssues": {
217
+ "F1": {
218
+ "severity": "Low",
219
+ "issue": "TimeRangePicker depends on the user-preference store + datetime filter, so it is reproduced (not rendered live) in Storybook."
220
+ },
221
+ "F2": {
222
+ "severity": "Low",
223
+ "a11y": true,
224
+ "issue": "Catalog-wide focus-ring removal (SF-001) may affect the picker inputs/preset rows; verify keyboard focus visibility."
225
+ }
226
+ },
227
+ "accessibility": {
228
+ "providedByAnt": "a-date-picker / a-time-picker expose a text input + a keyboard-navigable calendar/time panel; Escape closes; the input is focusable and typeable.",
229
+ "verify": "Focus-visible ring (SF-001) on inputs and on the TimeRangePicker preset rows (which are <a>/clickable divs — ensure they're keyboard-reachable and labelled)."
230
+ },
231
+ "scopedOut": {
232
+ "Scheduler / Recurrence (schedule-input/)": "17 files — a Once/Daily/Weekly/Monthly RECURRENCE builder (MRadioGroup + Once/Weekly/Monthly inputs). Date/time-adjacent but a DISTINCT component family — candidate for its own future entry, not part of date/time pickers.",
233
+ "DateTimePopover": "covered above (custom-range-only popover).",
234
+ "date-remark-pairs.vue": "a date + remark list (holidays / special days) — a composite, not a picker primitive.",
235
+ "TimeRangeSlider text-input shorthand (time-range-picker/text-input.vue)": "internal — typed shorthand ('5m','7d') with autocomplete inside TimeRangePicker.",
236
+ "kit DateRangePicker / NotifyTimePicker": "0× / niche — not used as standalone product patterns.",
237
+ "no week/month/quarter/calendar pickers": "confirmed absent — no MWeekPicker/MMonthPicker/MRangePicker/a-calendar usage."
238
+ },
239
+ "storybook": "Molecules/Date & Time Pickers",
240
+ "related": [
241
+ "dropdown-picker",
242
+ "form-item",
243
+ "popover"
244
+ ],
245
+ "figma": {
246
+ "status": "todo"
247
+ },
248
+ "apis": {
249
+ "TimeRangePicker": {
250
+ "source": "src/components/widgets/time-range-picker.vue",
251
+ "props": {
252
+ "value": "Object — selected range (.sync via change)",
253
+ "hideCustomTimeRange": "Boolean (false) — presets only",
254
+ "excludedOptions": "Array — preset keys to hide",
255
+ "allowClear": "Boolean (false)",
256
+ "bordered": "Boolean (false)",
257
+ "onlyLabel": "Boolean (false)",
258
+ "hideIcon": "Boolean (false)",
259
+ "hideSelectedTime": "Boolean (false)",
260
+ "disabled": "Boolean (false)",
261
+ "getPopupContainer": "Function"
262
+ },
263
+ "events": {
264
+ "change": "(range) — selection changed"
265
+ }
266
+ },
267
+ "MDatePicker": {
268
+ "source": "ui/components/Datepicker/Datepicker.vue (kit)",
269
+ "props": {
270
+ "value": "[String,Number,Object]",
271
+ "format": "String — display format",
272
+ "showTime": "[Boolean,Object] (default 12h hh:mm A) — date-time vs date-only",
273
+ "minDate": "[String,Number,Object]",
274
+ "maxDate": "[String,Number,Object]",
275
+ "disabledTime": "Function",
276
+ "placeholder": "('Select...')",
277
+ "getPopupContainer": "Function"
278
+ },
279
+ "events": {
280
+ "change": "(date)",
281
+ "update": "(date)",
282
+ "handleCalendarChange": "(date)"
283
+ }
284
+ }
285
+ },
286
+ "a11y": {
287
+ "summary": "Focusable text input + keyboard-navigable calendar; disabled via the disabled input. The trigger pill should expose its current selection to AT.",
288
+ "issues": [
289
+ "Preset rows are <a>/<div> — verify keyboard-reachable.",
290
+ "SF-001 focus ring.",
291
+ "Range error is shown as text."
292
+ ],
293
+ "doc": "Molecules/Date & Time Pickers/Accessibility",
294
+ "$note": "Catalogue-wide gap SF-001 = no visible :focus-visible ring; tracked in findings/."
295
+ }
296
+ }
@@ -0,0 +1,222 @@
1
+ {
2
+ "name": "drawer",
3
+ "component": "FlotoDrawer",
4
+ "display": "Drawer",
5
+ "tier": "organism",
6
+ "category": "overlay",
7
+ "source": "app",
8
+ "sourceFile": "src/components/_base-drawer.vue",
9
+ "status": "core",
10
+ "maturity": "stable",
11
+ "summary": "Slide-in side panel (from the right) for detail views, edit forms, and record-contextual content. The product's MOST-used overlay (FlotoDrawer 99x + FlotoDrawerForm 59x = 158x). Open via open prop or trigger slot.",
12
+ "variants": [
13
+ {
14
+ "name": "FlotoDrawer",
15
+ "what": "detail side panel",
16
+ "usage": "99×"
17
+ },
18
+ {
19
+ "name": "FlotoDrawerForm",
20
+ "what": "form + drawer (Add/Edit)",
21
+ "usage": "59×"
22
+ }
23
+ ],
24
+ "sizes": [
25
+ {
26
+ "name": "360px",
27
+ "value": "360px",
28
+ "note": "simple"
29
+ },
30
+ {
31
+ "name": "40%",
32
+ "value": "40%",
33
+ "note": "default"
34
+ },
35
+ {
36
+ "name": "50-70%",
37
+ "value": "50-70%",
38
+ "note": "richer"
39
+ },
40
+ {
41
+ "name": "85-96%",
42
+ "value": "85-96%",
43
+ "note": "large multi-pane"
44
+ }
45
+ ],
46
+ "states": [
47
+ {
48
+ "name": "open",
49
+ "what": "slid in from right"
50
+ },
51
+ {
52
+ "name": "closed",
53
+ "what": "hidden"
54
+ },
55
+ {
56
+ "name": "scrolling",
57
+ "what": "body scrolls, title/footer pinned"
58
+ }
59
+ ],
60
+ "family": "Overlay",
61
+ "components": {
62
+ "FlotoDrawer": {
63
+ "usage": 99,
64
+ "props": [
65
+ "open(toggles)",
66
+ "width(px/%, default 40%)",
67
+ "scrolledContent(default true -> FlotoScrollView)",
68
+ "usePadding",
69
+ "(a-drawer attrs: placement, ...)"
70
+ ],
71
+ "slots": [
72
+ "trigger {open,close,toggle}",
73
+ "title / title-row",
74
+ "default (body, scrollable)",
75
+ "actions {hide} (fixed footer)"
76
+ ],
77
+ "events": [
78
+ "show",
79
+ "hide"
80
+ ]
81
+ },
82
+ "FlotoDrawerForm": {
83
+ "usage": 59,
84
+ "summary": "FlotoForm (vertical) + FlotoDrawer; suppresses MForm default Submit so only the footer submits",
85
+ "slots": [
86
+ "trigger",
87
+ "header / header-row",
88
+ "default {submit}",
89
+ "actions {hide, submit}"
90
+ ]
91
+ }
92
+ },
93
+ "anatomy": [
94
+ "title (text-primary) + built-in close x",
95
+ "scrollable body (FlotoScrollView)",
96
+ "actions fixed footer (Cancel/Save)",
97
+ "dimmed backdrop"
98
+ ],
99
+ "behaviors": {
100
+ "open": "open prop (toggles) or trigger slot",
101
+ "close": "built-in x (Ant a-drawer) + actions footer + Escape — maskClosable:false (no backdrop-close)",
102
+ "scroll": "body scrolls (FlotoScrollView); title/footer pinned; ~65px reserved at bottom when actions slot present",
103
+ "hide-delay": "@hide fires ~500ms after close (slide-out animation)"
104
+ },
105
+ "tokensUsed": [
106
+ "--page-background-color",
107
+ "--border-color",
108
+ "--page-text-color"
109
+ ],
110
+ "accessibility": {
111
+ "semantics": "role=dialog, focus trap, BUILT-IN close x (better than MModal which has none), Escape closes",
112
+ "focus": "no visible ring inside (SF-001)"
113
+ },
114
+ "decisionFlow": [
115
+ "Short yes/no or destructive confirmation? -> FlotoConfirmModal, not a drawer.",
116
+ "Long content, a detail view, or a form contextual to a record? -> FlotoDrawer.",
117
+ "An Add/Edit FORM in a panel? -> FlotoDrawerForm (vertical form + validation + footer submit).",
118
+ "A short focused task that must center-interrupt? -> MModal (dialog)."
119
+ ],
120
+ "usageRules": {
121
+ "detail(FlotoDrawer)": {
122
+ "useWhen": "view details / long content contextual to a record",
123
+ "example": "incident details, view-detail/view-more drawers, gauge/compliance drilldown"
124
+ },
125
+ "form(FlotoDrawerForm)": {
126
+ "useWhen": "Add/Edit form in a panel (vertical form + validation; footer Save submits)",
127
+ "example": "most Add/Edit flows; approval drawers (sync/runbook/firmware), email-dispatch form"
128
+ },
129
+ "width": {
130
+ "useWhen": "default 40%; narrower (px) for simple panels, wider (%) for rich content",
131
+ "example": "360px detail, 60% rich editor"
132
+ },
133
+ "large-fullscreen(85-96%)": {
134
+ "useWhen": "complex MULTI-PANE flows (left nav + main form + right reference panel) — config/registration wizards",
135
+ "example": "APM Application Registration (width=96%); used ~30x at 90%/96%/85%/100%. Use a wide drawer, not a modal."
136
+ }
137
+ },
138
+ "inProduct": {
139
+ "FlotoDrawer": "IncidentDetails, ViewDetail/ViewMore, Gauge/Compliance drilldown, Credential/Catalog selection drawers",
140
+ "FlotoDrawerForm": "Add/Edit forms, SyncApproval/RunbookApproval/FirmwareUpgradeApproval, AttachRules/AttachStorage, EmailDispatch form"
141
+ },
142
+ "do": [
143
+ "Use a Drawer for detail views, edit forms, long/record-contextual content.",
144
+ "Use FlotoDrawerForm for Add/Edit forms (vertical form + footer submit).",
145
+ "Put actions in the footer (Cancel left, Save right)."
146
+ ],
147
+ "dont": [
148
+ "Don't use a Drawer for a short confirmation (use FlotoConfirmModal).",
149
+ "Don't rely on backdrop-click to close (disabled) — use the x or footer Cancel.",
150
+ "Don't add an MForm inside without suppressing its default Submit (two submit buttons)."
151
+ ],
152
+ "whenToUse": "Detail views, edit forms, and long/record-contextual content that doesn't fit a centered modal.",
153
+ "insteadOf": [
154
+ "raw a-drawer",
155
+ "a modal for long/record-contextual content"
156
+ ],
157
+ "usage": {
158
+ "FlotoDrawer": 99,
159
+ "FlotoDrawerForm": 59,
160
+ "total": 158,
161
+ "open": 27,
162
+ "width": 14
163
+ },
164
+ "knownIssues": [
165
+ "F1 (low): @hide emits ~500ms after close (setTimeout for the slide-out animation) — parents should account for the delay.",
166
+ "F2 (high, a11y): no visible focus ring inside the panel (SF-001).",
167
+ "F3 (low): .actions footer is justify-end with NO gap -> adjacent buttons touch (0px). Product convention is mr-2 on the non-last button. Fix: add gap:8px to .actions in drawer.less. Footer layout: 2=Cancel(mr-2)+Save right; 3=Reset/Cancel/Save right OR destructive left + confirm group right (justify-between); 4=tertiary/note left + confirm group right; primary far-right, Cancel just left of it, destructive/tertiary on the left."
168
+ ],
169
+ "related": [
170
+ "MModal",
171
+ "FlotoConfirmModal",
172
+ "FlotoDrawerForm",
173
+ "FlotoForm",
174
+ "FlotoFormItem",
175
+ "MButton"
176
+ ],
177
+ "changelog": [
178
+ "2026-06-08: added (decision-grade). FlotoDrawer (99x) + FlotoDrawerForm (59x) = 158x, the most-used overlay. open-prop/trigger; title/body(scrollable)/actions(fixed footer) slots; width 40%; built-in x + maskClosable:false. Verified Basic + Form drawer render. F1 (delayed hide), F2 (focus)."
179
+ ],
180
+ "storybook": "Organisms/Drawer",
181
+ "figma": {
182
+ "status": "todo",
183
+ "component": "Drawer"
184
+ },
185
+ "$tokensNote": "Title text = --page-text-color. Backdrop scrim is Ant's literal rgba(0,0,0,.45) — not tokenized; Ant Drawer internals follow the kit.",
186
+ "apis": {
187
+ "FlotoDrawer": {
188
+ "source": "src/components/_base-drawer.vue",
189
+ "props": {
190
+ "open": "Boolean (false) — visibility (use .sync)",
191
+ "width": "[String,Number] ('40%') — drawer width",
192
+ "usePadding": "Boolean (false) — pad the body",
193
+ "scrolledContent": "Boolean (true) — wrap the body in a scroll view"
194
+ },
195
+ "events": {
196
+ "show": "() — opened",
197
+ "hide": "() — closed (backdrop / ESC / close)"
198
+ },
199
+ "slots": {
200
+ "default": "drawer body"
201
+ }
202
+ },
203
+ "FlotoDrawerForm": {
204
+ "source": "src/components/crud/_base-drawer-form.vue",
205
+ "note": "Form-in-drawer variant — same open / width / scrolledContent props.",
206
+ "slots": {
207
+ "trigger": "element that opens the drawer",
208
+ "header": "custom header",
209
+ "header-row": "header action row",
210
+ "default": "form body"
211
+ }
212
+ }
213
+ },
214
+ "a11y": {
215
+ "summary": "Ant a-drawer → role=dialog; focus trapped + restored on close; built-in close ×; Esc closes; backdrop-click doesn't close (maskClosable:false). Give a clear title.",
216
+ "issues": [
217
+ "No visible focus ring inside the panel."
218
+ ],
219
+ "doc": "Organisms/Drawer/Accessibility",
220
+ "$note": "Catalogue-wide gap SF-001 = no visible :focus-visible ring; tracked in findings/."
221
+ }
222
+ }