@mangtre/ui 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 (95) hide show
  1. package/LICENSE +26 -0
  2. package/dist/MangThemeProvider-BqdGKBP2.d.ts +34 -0
  3. package/dist/Money-Dw3GnUvX.d.ts +27 -0
  4. package/dist/actions.d.ts +31 -0
  5. package/dist/actions.js +13 -0
  6. package/dist/ai.d.ts +58 -0
  7. package/dist/ai.js +13 -0
  8. package/dist/analytics.d.ts +40 -0
  9. package/dist/analytics.js +15 -0
  10. package/dist/app.d.ts +24 -0
  11. package/dist/app.js +9 -0
  12. package/dist/catalog.d.ts +76 -0
  13. package/dist/catalog.js +1210 -0
  14. package/dist/charts.d.ts +89 -0
  15. package/dist/charts.js +34 -0
  16. package/dist/chunk-3AL4SUFD.js +301 -0
  17. package/dist/chunk-4XNSYKQE.js +142 -0
  18. package/dist/chunk-5Z4VLQKH.js +43 -0
  19. package/dist/chunk-7P2EQZYD.js +59 -0
  20. package/dist/chunk-7WHNIEDV.js +120 -0
  21. package/dist/chunk-ASZKHSMG.js +82 -0
  22. package/dist/chunk-BCBN2EGH.js +216 -0
  23. package/dist/chunk-BLYAFV45.js +320 -0
  24. package/dist/chunk-DLKEXWPA.js +90 -0
  25. package/dist/chunk-DTASXPTB.js +70 -0
  26. package/dist/chunk-FZRXVRC7.js +63 -0
  27. package/dist/chunk-ID233AGM.js +108 -0
  28. package/dist/chunk-IVYXOKMO.js +74 -0
  29. package/dist/chunk-IX3DYETF.js +61 -0
  30. package/dist/chunk-JJB4PJC3.js +166 -0
  31. package/dist/chunk-K5Q3RCV6.js +119 -0
  32. package/dist/chunk-LNRUPJDF.js +161 -0
  33. package/dist/chunk-LZORNMBL.js +0 -0
  34. package/dist/chunk-OBPXCUVF.js +282 -0
  35. package/dist/chunk-OJX2EIMB.js +145 -0
  36. package/dist/chunk-PPOYMKV3.js +170 -0
  37. package/dist/chunk-PQGUWJG4.js +47 -0
  38. package/dist/chunk-RE7OWRA4.js +187 -0
  39. package/dist/chunk-SJF3CHAW.js +108 -0
  40. package/dist/chunk-UF6ANDJZ.js +112 -0
  41. package/dist/chunk-VGC5DMOM.js +107 -0
  42. package/dist/chunk-VP56Z4BS.js +0 -0
  43. package/dist/chunk-VRD66FIA.js +77 -0
  44. package/dist/chunk-X7T2DJLU.js +113 -0
  45. package/dist/chunk-XPV3OOLU.js +147 -0
  46. package/dist/chunk-YN5O6YL6.js +69 -0
  47. package/dist/chunk-Z4ANGBPC.js +94 -0
  48. package/dist/creator.d.ts +55 -0
  49. package/dist/creator.js +20 -0
  50. package/dist/data-room.d.ts +50 -0
  51. package/dist/data-room.js +17 -0
  52. package/dist/editor.d.ts +32 -0
  53. package/dist/editor.js +14 -0
  54. package/dist/feedback.d.ts +48 -0
  55. package/dist/feedback.js +16 -0
  56. package/dist/forms.d.ts +91 -0
  57. package/dist/forms.js +26 -0
  58. package/dist/handoff.d.ts +37 -0
  59. package/dist/handoff.js +13 -0
  60. package/dist/index.css +2 -0
  61. package/dist/index.d.ts +62 -0
  62. package/dist/index.js +338 -0
  63. package/dist/layout.d.ts +57 -0
  64. package/dist/layout.js +22 -0
  65. package/dist/learning.d.ts +46 -0
  66. package/dist/learning.js +15 -0
  67. package/dist/media.d.ts +48 -0
  68. package/dist/media.js +16 -0
  69. package/dist/monetization.d.ts +30 -0
  70. package/dist/monetization.js +14 -0
  71. package/dist/money.d.ts +45 -0
  72. package/dist/money.js +28 -0
  73. package/dist/navigation.d.ts +36 -0
  74. package/dist/navigation.js +14 -0
  75. package/dist/overlay.d.ts +72 -0
  76. package/dist/overlay.js +20 -0
  77. package/dist/platform.d.ts +94 -0
  78. package/dist/platform.js +42 -0
  79. package/dist/primitives.d.ts +83 -0
  80. package/dist/primitives.js +22 -0
  81. package/dist/privacy.d.ts +28 -0
  82. package/dist/privacy.js +15 -0
  83. package/dist/sandbox.d.ts +40 -0
  84. package/dist/sandbox.js +15 -0
  85. package/dist/settings.d.ts +29 -0
  86. package/dist/settings.js +13 -0
  87. package/dist/surface.d.ts +33 -0
  88. package/dist/surface.js +16 -0
  89. package/dist/theme.css +63 -0
  90. package/dist/theme.d.ts +64 -0
  91. package/dist/theme.js +27 -0
  92. package/dist/tokens.css +119 -0
  93. package/dist/tokens.d.ts +128 -0
  94. package/dist/tokens.js +8 -0
  95. package/package.json +151 -0
@@ -0,0 +1,1210 @@
1
+ // src/catalog/areas/core.ts
2
+ var CORE_ENTRIES = [
3
+ // ---- theme ----
4
+ {
5
+ id: "theme/MangThemeProvider",
6
+ area: "theme",
7
+ name: "MangThemeProvider",
8
+ importPath: "@mangtre/ui",
9
+ status: "stable",
10
+ since: "P0",
11
+ version: "0.1.0",
12
+ summary: {
13
+ vi: "C\u1EA5p theme/locale/density cho m\u1ECDi component @mangtre/ui (ch\u1EC9 context, kh\xF4ng b\u1ECDc DOM). B\u1ECDc c\xE2y con b\u1EB1ng theme='dark' \u0111\u1EC3 \u0111\u1EB7t l\xEAn n\u1EC1n t\u1ED1i c\u1EE7a shell; m\u1EB7c \u0111\u1ECBnh 'light' (ru\u1ED9t mini-app).",
14
+ en: "Provides theme/locale/density to all @mangtre/ui components (context only, no DOM). Wrap a subtree with theme='dark' for the shell surface; default 'light' (mini-app interior)."
15
+ },
16
+ code: `import { MangThemeProvider, Button } from "@mangtre/ui";
17
+
18
+ <MangThemeProvider theme="light" locale="vi">
19
+ <Button>L\u01B0u</Button>
20
+ </MangThemeProvider>`,
21
+ props: [
22
+ {
23
+ name: "theme",
24
+ type: '"light" | "dark" | "auto"',
25
+ default: '"light"',
26
+ desc: { vi: "B\u1EC1 m\u1EB7t s\xE1ng/t\u1ED1i", en: "Surface theme" }
27
+ },
28
+ {
29
+ name: "locale",
30
+ type: '"vi" | "en"',
31
+ default: '"vi"',
32
+ desc: { vi: "Ng\xF4n ng\u1EEF nh\xE3n", en: "Label language" }
33
+ },
34
+ {
35
+ name: "density",
36
+ type: '"comfortable" | "compact" | "touch"',
37
+ default: '"comfortable"',
38
+ desc: { vi: "M\u1EADt \u0111\u1ED9 control", en: "Control density" }
39
+ }
40
+ ],
41
+ related: ["theme/hooks"]
42
+ },
43
+ {
44
+ id: "theme/hooks",
45
+ area: "theme",
46
+ name: "useMangTheme, useLocale, useDensity, useScope, useKitStyles, cx",
47
+ importPath: "@mangtre/ui",
48
+ status: "stable",
49
+ since: "P0",
50
+ version: "0.1.0",
51
+ summary: {
52
+ vi: "Hook n\u1EC1n cho component t\u1EF1 d\u1EF1ng: \u0111\u1ECDc theme/locale/density, mount CSS scoped, gh\xE9p className. H\u1EA7u h\u1EBFt app KH\xD4NG c\u1EA7n \u2014 ch\u1EC9 khi t\u1EF1 vi\u1EBFt component theo chu\u1EA9n M\u0103ng.",
53
+ en: "Low-level hooks for building components: read theme/locale/density, mount scoped CSS, join classNames. Most apps don't need these \u2014 only when authoring M\u0103ng-style components."
54
+ },
55
+ code: `import { useScope, useKitStyles, cx } from "@mangtre/ui";
56
+
57
+ function Thing({ theme }) {
58
+ useKitStyles();
59
+ const scope = useScope(theme);
60
+ return <div {...scope} className={cx(scope.className, "my-thing")} />;
61
+ }`
62
+ },
63
+ {
64
+ id: "theme/MangApp",
65
+ area: "theme",
66
+ name: "MangApp",
67
+ importPath: "@mangtre/ui",
68
+ status: "stable",
69
+ since: "P2",
70
+ version: "0.1.0",
71
+ summary: {
72
+ vi: "B\u1ECDc m\u1ED9t-d\xF2ng cho RU\u1ED8T mini-app: g\u1ED9p MangThemeProvider + m\u1ED9t root .mang-ui-scope th\u1EADt (\u0111\u1EC3 m\u1ECDi var(--m-*) ph\xE2n gi\u1EA3i, k\u1EC3 c\u1EA3 markup th\xF4) + ToastProvider, v\xE0 (tu\u1EF3 ch\u1ECDn) chi\u1EBFu sdk.theme qua applyThemeVars. M\u1EB7c \u0111\u1ECBnh theme='light' (AD v0.2 \xA79).",
73
+ en: "One-line wrapper for a mini-app INTERIOR: MangThemeProvider + a real .mang-ui-scope root (so every var(--m-*) resolves, even raw markup) + ToastProvider, and optionally projects sdk.theme via applyThemeVars. Default theme='light' (AD v0.2 \xA79)."
74
+ },
75
+ code: `import { MangApp } from "@mangtre/ui";
76
+ import { createRoot } from "react-dom/client";
77
+
78
+ export const mount = (root, sdk) => {
79
+ const r = createRoot(root);
80
+ r.render(
81
+ <MangApp locale={sdk.locale} sdkTheme={sdk.theme}>
82
+ <App storage={sdk.storage} />
83
+ </MangApp>,
84
+ );
85
+ return () => r.unmount();
86
+ };`,
87
+ props: [
88
+ {
89
+ name: "theme",
90
+ type: '"light" | "dark" | "auto"',
91
+ default: '"light"',
92
+ desc: { vi: "B\u1EC1 m\u1EB7t ru\u1ED9t app", en: "Interior surface" }
93
+ },
94
+ {
95
+ name: "locale",
96
+ type: '"vi" | "en"',
97
+ default: '"vi"',
98
+ desc: { vi: "Truy\u1EC1n sdk.locale", en: "Pass sdk.locale" }
99
+ },
100
+ {
101
+ name: "sdkTheme",
102
+ type: "ThemeTokens",
103
+ desc: { vi: "S\u1EE3i accent runtime (sdk.theme)", en: "Runtime accent thread (sdk.theme)" }
104
+ },
105
+ {
106
+ name: "withToasts",
107
+ type: "boolean",
108
+ default: "true",
109
+ desc: { vi: "Mount ToastProvider", en: "Mount ToastProvider" }
110
+ }
111
+ ],
112
+ related: ["theme/MangThemeProvider", "overlay/Toast"]
113
+ },
114
+ // ---- primitives ----
115
+ {
116
+ id: "primitives/Stack",
117
+ area: "primitives",
118
+ name: "Box, Stack, HStack, VStack, Spacer",
119
+ importPath: "@mangtre/ui",
120
+ status: "stable",
121
+ since: "P0",
122
+ version: "0.1.0",
123
+ summary: {
124
+ vi: "Kh\u1ED1i layout flex theo thang spacing token. HStack=h\xE0ng, VStack=c\u1ED9t, Spacer \u0111\u1EA9y gi\xE3n. Kh\xF4ng m\xE0u n\xEAn kh\xF4ng c\u1EA7n theme.",
125
+ en: "Flex layout built on the token spacing scale. HStack=row, VStack=column, Spacer pushes apart. No color, so no theme needed."
126
+ },
127
+ code: `import { VStack, HStack, Spacer, Button } from "@mangtre/ui";
128
+
129
+ <VStack gap={4}>
130
+ <HStack gap={2}>
131
+ <Button>L\u01B0u</Button>
132
+ <Spacer />
133
+ <Button variant="ghost">Hu\u1EF7</Button>
134
+ </HStack>
135
+ </VStack>`,
136
+ props: [
137
+ {
138
+ name: "gap",
139
+ type: "0\u201316 (token step)",
140
+ default: "4",
141
+ desc: { vi: "Kho\u1EA3ng c\xE1ch gi\u1EEFa con", en: "Gap between children" }
142
+ },
143
+ {
144
+ name: "direction",
145
+ type: '"row" | "column"',
146
+ default: '"column"',
147
+ desc: { vi: "Tr\u1EE5c ch\xEDnh (Stack)", en: "Main axis (Stack)" }
148
+ },
149
+ {
150
+ name: "align / justify / wrap",
151
+ type: "CSS values / boolean",
152
+ desc: { vi: "C\u0103n ch\u1EC9nh flex", en: "Flex alignment" }
153
+ }
154
+ ]
155
+ },
156
+ {
157
+ id: "primitives/Text",
158
+ area: "primitives",
159
+ name: "Text, Heading",
160
+ importPath: "@mangtre/ui",
161
+ status: "stable",
162
+ since: "P0",
163
+ version: "0.1.0",
164
+ summary: {
165
+ vi: "Ch\u1EEF th\xE2n (Text, tone default/muted/strong/accent) v\xE0 ti\xEAu \u0111\u1EC1 (Heading level 1\u20136, font Quicksand). T\u1EF1 \u0111\xFAng m\xE0u theo theme.",
166
+ en: "Body text (Text, tone default/muted/strong/accent) and headings (Heading level 1\u20136, Quicksand). Theme-correct color."
167
+ },
168
+ code: `import { Heading, Text } from "@mangtre/ui";
169
+
170
+ <Heading level={2}>Nh\xF3m c\u1EE7a b\u1EA1n</Heading>
171
+ <Text tone="muted" size="sm">Ch\u1EA1y offline, d\u1EEF li\u1EC7u tr\xEAn m\xE1y.</Text>`,
172
+ props: [
173
+ {
174
+ name: "tone",
175
+ type: '"default" | "muted" | "strong" | "accent"',
176
+ default: '"default"',
177
+ desc: { vi: "S\u1EAFc \u0111\u1ED9 ch\u1EEF (Text)", en: "Text tone" }
178
+ },
179
+ {
180
+ name: "size",
181
+ type: "xs \u2026 3xl",
182
+ default: '"base"',
183
+ desc: { vi: "C\u1EE1 ch\u1EEF token", en: "Font-size token" }
184
+ },
185
+ {
186
+ name: "level",
187
+ type: "1\u20136",
188
+ default: "2",
189
+ desc: { vi: "C\u1EA5p ti\xEAu \u0111\u1EC1 (Heading)", en: "Heading level" }
190
+ }
191
+ ]
192
+ },
193
+ {
194
+ id: "primitives/Divider",
195
+ area: "primitives",
196
+ name: "Divider",
197
+ importPath: "@mangtre/ui",
198
+ status: "stable",
199
+ since: "P0",
200
+ version: "0.1.0",
201
+ summary: {
202
+ vi: "\u0110\u01B0\u1EDDng k\u1EBB m\u1EA3nh theo m\xE0u line c\u1EE7a theme.",
203
+ en: "Hairline rule in the theme's line color."
204
+ },
205
+ code: `import { Divider } from "@mangtre/ui";
206
+
207
+ <Divider />`
208
+ },
209
+ // ---- actions ----
210
+ {
211
+ id: "actions/Button",
212
+ area: "actions",
213
+ name: "Button",
214
+ importPath: "@mangtre/ui",
215
+ status: "stable",
216
+ since: "P0",
217
+ version: "0.1.0",
218
+ summary: {
219
+ vi: "N\xFAt h\xE0nh \u0111\u1ED9ng. primary = n\u1EC1n xanh tre; secondary = vi\u1EC1n; ghost = trong su\u1ED1t; danger = \u0111\u1ECF. C\u1EE1 sm/md/lg, c\xF3 icon + block.",
220
+ en: "Action button. primary = bamboo-green fill; secondary = outline; ghost; danger = red. Sizes sm/md/lg, supports icon + block."
221
+ },
222
+ code: `import { Button } from "@mangtre/ui";
223
+
224
+ <Button onClick={save}>L\u01B0u</Button>
225
+ <Button variant="secondary">Hu\u1EF7</Button>
226
+ <Button variant="danger" icon="\u{1F5D1}">Xo\xE1</Button>`,
227
+ props: [
228
+ {
229
+ name: "variant",
230
+ type: '"primary" | "secondary" | "ghost" | "danger"',
231
+ default: '"primary"',
232
+ desc: { vi: "Ki\u1EC3u n\xFAt", en: "Style" }
233
+ },
234
+ {
235
+ name: "size",
236
+ type: '"sm" | "md" | "lg"',
237
+ default: '"md"',
238
+ desc: { vi: "K\xEDch c\u1EE1", en: "Size" }
239
+ },
240
+ { name: "icon", type: "ReactNode", desc: { vi: "Icon \u0111\u1EE9ng tr\u01B0\u1EDBc", en: "Leading icon" } },
241
+ { name: "block", type: "boolean", desc: { vi: "Gi\xE3n h\u1EBFt chi\u1EC1u ngang", en: "Full width" } }
242
+ ],
243
+ related: ["actions/IconButton"]
244
+ },
245
+ {
246
+ id: "actions/IconButton",
247
+ area: "actions",
248
+ name: "IconButton, ButtonGroup",
249
+ importPath: "@mangtre/ui",
250
+ status: "stable",
251
+ since: "P0",
252
+ version: "0.1.0",
253
+ summary: {
254
+ vi: "IconButton: n\xFAt vu\xF4ng ch\u1EC9-icon (b\u1EAFt bu\u1ED9c `label` cho a11y). ButtonGroup: h\xE0ng n\xFAt c\xE1ch \u0111\u1EC1u.",
255
+ en: "IconButton: square icon-only button (requires `label` for a11y). ButtonGroup: an evenly-spaced row."
256
+ },
257
+ code: `import { IconButton, ButtonGroup, Button } from "@mangtre/ui";
258
+
259
+ <ButtonGroup>
260
+ <Button>L\u01B0u</Button>
261
+ <IconButton label="L\xE0m m\u1EDBi" variant="secondary">\u21BB</IconButton>
262
+ </ButtonGroup>`,
263
+ props: [
264
+ {
265
+ name: "label",
266
+ type: "string",
267
+ required: true,
268
+ desc: { vi: "Nh\xE3n cho screen-reader", en: "Accessible label" }
269
+ }
270
+ ]
271
+ },
272
+ // ---- forms ----
273
+ {
274
+ id: "forms/Field",
275
+ area: "forms",
276
+ name: "Field",
277
+ importPath: "@mangtre/ui",
278
+ status: "stable",
279
+ since: "P0",
280
+ version: "0.1.0",
281
+ summary: {
282
+ vi: "B\u1ECDc 1 control v\u1EDBi label/m\xF4 t\u1EA3/l\u1ED7i; t\u1EF1 n\u1ED1i id + aria. \u0110\u1EB7t control l\xE0m con duy nh\u1EA5t.",
283
+ en: "Wraps one control with label/description/error; auto-wires id + aria. Pass the control as the single child."
284
+ },
285
+ code: `import { Field, Input } from "@mangtre/ui";
286
+
287
+ <Field label="T\xEAn nh\xF3m" description="Hi\u1EC3n th\u1ECB cho m\u1ECDi ng\u01B0\u1EDDi" required>
288
+ <Input placeholder="VD: \u0110i \u0110\xE0 L\u1EA1t" />
289
+ </Field>`,
290
+ props: [
291
+ {
292
+ name: "label / description / error",
293
+ type: "ReactNode",
294
+ desc: { vi: "Nh\xE3n / m\xF4 t\u1EA3 / l\u1ED7i", en: "Label / desc / error" }
295
+ },
296
+ { name: "required", type: "boolean", desc: { vi: "Hi\u1EC7n d\u1EA5u *", en: "Show *" } }
297
+ ]
298
+ },
299
+ {
300
+ id: "forms/Input",
301
+ area: "forms",
302
+ name: "Input, TextArea, Select",
303
+ importPath: "@mangtre/ui",
304
+ status: "stable",
305
+ since: "P0",
306
+ version: "0.1.0",
307
+ summary: {
308
+ vi: "Control nh\u1EADp li\u1EC7u chu\u1EA9n (`.mang-control`): Input, TextArea, Select. Nh\u1EADn m\u1ECDi thu\u1ED9c t\xEDnh HTML g\u1ED1c; gh\xE9p v\u1EDBi Field.",
309
+ en: "Standard text controls (`.mang-control`): Input, TextArea, Select. Accept all native HTML attrs; pair with Field."
310
+ },
311
+ code: `import { Input, TextArea, Select } from "@mangtre/ui";
312
+
313
+ <Input value={name} onChange={(e) => setName(e.target.value)} />
314
+ <TextArea rows={3} />
315
+ <Select value={bank} onChange={...}>
316
+ <option value="vcb">Vietcombank</option>
317
+ </Select>`
318
+ },
319
+ {
320
+ id: "forms/Toggle",
321
+ area: "forms",
322
+ name: "Checkbox, Switch",
323
+ importPath: "@mangtre/ui",
324
+ status: "stable",
325
+ since: "P0",
326
+ version: "0.1.0",
327
+ summary: {
328
+ vi: "Control nh\u1ECB ph\xE2n: Checkbox (\u0111\xE1nh d\u1EA5u) v\xE0 Switch (b\u1EADt/t\u1EAFt). \u0110\u1EC1u b\u1ECDc input g\u1ED1c n\xEAn accessible + controlled qua checked/onChange.",
329
+ en: "Boolean controls: Checkbox and Switch. Both wrap a native input \u2014 accessible + controlled via checked/onChange."
330
+ },
331
+ code: `import { Checkbox, Switch } from "@mangtre/ui";
332
+
333
+ <Checkbox label="\u0110\xE3 thanh to\xE1n" checked={paid} onChange={(e) => setPaid(e.target.checked)} />
334
+ <Switch label="Ph\xF2ng tr\u1EF1c ti\u1EBFp" checked={live} onChange={(e) => setLive(e.target.checked)} />`
335
+ },
336
+ {
337
+ id: "forms/SegmentedControl",
338
+ area: "forms",
339
+ name: "SegmentedControl",
340
+ importPath: "@mangtre/ui",
341
+ status: "stable",
342
+ since: "P2",
343
+ version: "0.1.0",
344
+ summary: {
345
+ vi: "Chuy\u1EC3n ch\u1EBF \u0111\u1ED9/b\u1ED9 l\u1ECDc m\u1ED9t-l\u1EF1a-ch\u1ECDn (ki\u1EC3u radio, n\xFAt li\u1EC1n nhau). D\xF9ng cho 2\u20134 ch\u1EBF \u0111\u1ED9 lo\u1EA1i tr\u1EEB nhau (vd 'H\xF4m nay / Tu\u1EA7n', 'T\u1EADp / \u0102n').",
346
+ en: "Single-select mode/filter toggle (radio-like, flush buttons). For 2\u20134 mutually exclusive modes (e.g. 'Today / Week', 'Workout / Meal')."
347
+ },
348
+ code: `import { SegmentedControl } from "@mangtre/ui";
349
+
350
+ <SegmentedControl
351
+ ariaLabel="Ch\u1EBF \u0111\u1ED9 xem"
352
+ value={view}
353
+ onChange={setView}
354
+ options={[
355
+ { value: "today", label: "H\xF4m nay" },
356
+ { value: "week", label: "Tu\u1EA7n" },
357
+ ]}
358
+ />`,
359
+ props: [
360
+ {
361
+ name: "options",
362
+ type: "{ value: string; label: ReactNode; disabled?: boolean }[]",
363
+ required: true,
364
+ desc: { vi: "Danh s\xE1ch l\u1EF1a ch\u1ECDn", en: "Option list" }
365
+ },
366
+ {
367
+ name: "value",
368
+ type: "string",
369
+ required: true,
370
+ desc: { vi: "Gi\xE1 tr\u1ECB \u0111ang ch\u1ECDn", en: "Selected value" }
371
+ },
372
+ {
373
+ name: "onChange",
374
+ type: "(value: string) => void",
375
+ required: true,
376
+ desc: { vi: "Khi \u0111\u1ED5i l\u1EF1a ch\u1ECDn", en: "On selection change" }
377
+ },
378
+ {
379
+ name: "size",
380
+ type: '"sm" | "md"',
381
+ default: '"md"',
382
+ desc: { vi: "K\xEDch th\u01B0\u1EDBc", en: "Size" }
383
+ }
384
+ ],
385
+ related: ["navigation/Tabs"]
386
+ },
387
+ {
388
+ id: "forms/Chip",
389
+ area: "forms",
390
+ name: "Chip",
391
+ importPath: "@mangtre/ui",
392
+ status: "stable",
393
+ since: "P2",
394
+ version: "0.1.0",
395
+ summary: {
396
+ vi: "Pill g\u1ECDn \u2014 ch\u1ECDn \u0111\u01B0\u1EE3c (onClick) v\xE0/ho\u1EB7c xo\xE1 \u0111\u01B0\u1EE3c (onRemove). D\xF9ng cho tag, b\u1ED9 l\u1ECDc nhanh, ch\u1ECDn nhi\u1EC1u (m\xF4n \u0103n/b\xE0i t\u1EADp).",
397
+ en: "Compact pill \u2014 selectable (onClick) and/or removable (onRemove). For tags, quick filters, multi-pick (meals/exercises)."
398
+ },
399
+ code: `import { Chip } from "@mangtre/ui";
400
+
401
+ <Chip selected={on} onClick={() => setOn(!on)}>C\xE0 ph\xEA</Chip>
402
+ <Chip onRemove={() => removeTag(t)}>{t}</Chip>`,
403
+ props: [
404
+ {
405
+ name: "selected",
406
+ type: "boolean",
407
+ desc: { vi: "Tr\u1EA1ng th\xE1i ch\u1ECDn", en: "Selected state" }
408
+ },
409
+ {
410
+ name: "onClick",
411
+ type: "() => void",
412
+ desc: { vi: "Bi\u1EBFn chip th\xE0nh n\xFAt toggle", en: "Make the chip a toggle button" }
413
+ },
414
+ {
415
+ name: "onRemove",
416
+ type: "() => void",
417
+ desc: { vi: "Th\xEAm n\xFAt \xD7 \u0111\u1EC3 xo\xE1", en: "Add a trailing \xD7 remove button" }
418
+ }
419
+ ]
420
+ },
421
+ {
422
+ id: "forms/NumberStepper",
423
+ area: "forms",
424
+ name: "NumberStepper",
425
+ importPath: "@mangtre/ui",
426
+ status: "stable",
427
+ since: "P2",
428
+ version: "0.1.0",
429
+ summary: {
430
+ vi: "Nh\u1EADp s\u1ED1 v\u1EDBi n\xFAt \u2212/+, k\u1EB9p trong [min,max]. D\xF9ng cho s\u1ED1 l\u01B0\u1EE3ng, s\u1ED1 l\u1EA7n (reps), \u0111\u1EBFm.",
431
+ en: "Numeric entry with \u2212/+ buttons, clamped to [min,max]. For quantities, reps, counts."
432
+ },
433
+ code: `import { NumberStepper } from "@mangtre/ui";
434
+
435
+ <NumberStepper ariaLabel="S\u1ED1 l\u1EA7n" value={reps} onChange={setReps} min={0} max={99} />`,
436
+ props: [
437
+ {
438
+ name: "value",
439
+ type: "number",
440
+ required: true,
441
+ desc: { vi: "Gi\xE1 tr\u1ECB", en: "Value" }
442
+ },
443
+ {
444
+ name: "onChange",
445
+ type: "(value: number) => void",
446
+ required: true,
447
+ desc: { vi: "Khi \u0111\u1ED5i gi\xE1 tr\u1ECB (\u0111\xE3 k\u1EB9p)", en: "On change (clamped)" }
448
+ },
449
+ {
450
+ name: "min / max / step",
451
+ type: "number",
452
+ desc: { vi: "Gi\u1EDBi h\u1EA1n + b\u01B0\u1EDBc nh\u1EA3y", en: "Bounds + step" }
453
+ }
454
+ ]
455
+ },
456
+ // ---- surface ----
457
+ {
458
+ id: "surface/Card",
459
+ area: "surface",
460
+ name: "Card, CardHeader, CardBody, CardFooter, Panel",
461
+ importPath: "@mangtre/ui",
462
+ status: "stable",
463
+ since: "P0",
464
+ version: "0.1.0",
465
+ summary: {
466
+ vi: "Card: b\u1EC1 m\u1EB7t n\u1ED9i dung ch\xEDnh (c\xF3 Header/Body/Footer, `interactive` \u0111\u1EC3 hover n\u1EA3y). Panel: b\u1EC1 m\u1EB7t ch\xECm gom n\u1ED9i dung ph\u1EE5.",
467
+ en: "Card: primary content surface (Header/Body/Footer, `interactive` for hover lift). Panel: a sunken surface for secondary content."
468
+ },
469
+ code: `import { Card, CardHeader, CardBody, CardFooter, Heading, Button } from "@mangtre/ui";
470
+
471
+ <Card>
472
+ <CardHeader><Heading level={4}>Nh\xF3m c\u1EE7a b\u1EA1n</Heading></CardHeader>
473
+ <CardBody>3 ng\u01B0\u1EDDi \xB7 3 kho\u1EA3n</CardBody>
474
+ <CardFooter><Button size="sm">Xem ai n\u1EE3 ai</Button></CardFooter>
475
+ </Card>`,
476
+ props: [
477
+ {
478
+ name: "interactive",
479
+ type: "boolean",
480
+ desc: { vi: "Hover n\u1EA3y + con tr\u1ECF pointer", en: "Hover lift + pointer" }
481
+ }
482
+ ]
483
+ },
484
+ // ---- navigation ----
485
+ {
486
+ id: "navigation/Tabs",
487
+ area: "navigation",
488
+ name: "Tabs, TabList, Tab, TabPanel",
489
+ importPath: "@mangtre/ui",
490
+ status: "stable",
491
+ since: "P0",
492
+ version: "0.1.0",
493
+ summary: {
494
+ vi: "Tab g\u1ED9p (controlled qua value/onValueChange, ho\u1EB7c defaultValue). B\xE0n ph\xEDm \u2190/\u2192/Home/End. TabPanel hi\u1EC7n theo value.",
495
+ en: "Compound tabs (controlled via value/onValueChange, or defaultValue). Keyboard \u2190/\u2192/Home/End. TabPanel shows by value."
496
+ },
497
+ code: `import { Tabs, TabList, Tab, TabPanel } from "@mangtre/ui";
498
+
499
+ <Tabs defaultValue="a">
500
+ <TabList label="V\xED d\u1EE5">
501
+ <Tab value="a">T\u1ED5ng quan</Tab>
502
+ <Tab value="b">Kho\u1EA3n chi</Tab>
503
+ </TabList>
504
+ <TabPanel value="a">\u2026</TabPanel>
505
+ <TabPanel value="b">\u2026</TabPanel>
506
+ </Tabs>`
507
+ },
508
+ // ---- overlay ----
509
+ {
510
+ id: "overlay/Dialog",
511
+ area: "overlay",
512
+ name: "Dialog",
513
+ importPath: "@mangtre/ui",
514
+ status: "stable",
515
+ since: "P0",
516
+ version: "0.1.0",
517
+ summary: {
518
+ vi: "Modal gi\u1EEFa m\xE0n (in-tree fixed, KH\xD4NG portal \u2192 kh\xF4ng b\u1ECB `container-type` nh\u1ED1t). B\u1EABy focus, Esc/n\u1EC1n \u0111\xF3ng. \u0110i\u1EC1u khi\u1EC3n qua open/onClose.",
519
+ en: "Centered modal (in-tree fixed, NOT a portal \u2192 never trapped by `container-type`). Focus-trapped, Esc/backdrop close. Controlled via open/onClose."
520
+ },
521
+ code: `import { Dialog, Button } from "@mangtre/ui";
522
+
523
+ <Dialog open={open} onClose={() => setOpen(false)} title="Xo\xE1 nh\xF3m?"
524
+ footer={<><Button variant="ghost" onClick={close}>Hu\u1EF7</Button>
525
+ <Button variant="danger" onClick={remove}>Xo\xE1</Button></>}>
526
+ H\xE0nh \u0111\u1ED9ng n\xE0y kh\xF4ng th\u1EC3 ho\xE0n t\xE1c.
527
+ </Dialog>`,
528
+ props: [
529
+ { name: "open", type: "boolean", required: true, desc: { vi: "Hi\u1EC3n th\u1ECB", en: "Visible" } },
530
+ {
531
+ name: "onClose",
532
+ type: "() => void",
533
+ required: true,
534
+ desc: { vi: "Khi \u0111\xF3ng", en: "On close" }
535
+ },
536
+ {
537
+ name: "title / footer",
538
+ type: "ReactNode",
539
+ desc: { vi: "Ti\xEAu \u0111\u1EC1 / ch\xE2n", en: "Title / footer" }
540
+ }
541
+ ],
542
+ related: ["overlay/Sheet", "overlay/Toast"]
543
+ },
544
+ {
545
+ id: "overlay/Sheet",
546
+ area: "overlay",
547
+ name: "Sheet",
548
+ importPath: "@mangtre/ui",
549
+ status: "stable",
550
+ since: "P0",
551
+ version: "0.1.0",
552
+ summary: {
553
+ vi: "Ng\u0103n k\xE9o tr\u01B0\u1EE3t t\u1EEB c\u1EA1nh (right/left/bottom). C\xF9ng c\u01A1 ch\u1EBF overlay nh\u01B0 Dialog.",
554
+ en: "Drawer sliding from an edge (right/left/bottom). Same overlay behavior as Dialog."
555
+ },
556
+ code: `import { Sheet } from "@mangtre/ui";
557
+
558
+ <Sheet open={open} onClose={close} side="right" title="B\u1ED9 l\u1ECDc">\u2026</Sheet>`,
559
+ props: [
560
+ {
561
+ name: "side",
562
+ type: '"right" | "left" | "bottom"',
563
+ default: '"right"',
564
+ desc: { vi: "C\u1EA1nh g\u1EAFn", en: "Edge" }
565
+ }
566
+ ]
567
+ },
568
+ {
569
+ id: "overlay/Toast",
570
+ area: "overlay",
571
+ name: "ToastProvider, useToast",
572
+ importPath: "@mangtre/ui",
573
+ status: "stable",
574
+ since: "P0",
575
+ version: "0.1.0",
576
+ summary: {
577
+ vi: "Th\xF4ng b\xE1o n\u1ED5i. B\u1ECDc app b\u1EB1ng ToastProvider, r\u1ED3i g\u1ECDi toast({message, tone}) t\u1EEB useToast(). T\u1EF1 \u1EA9n sau 4s.",
578
+ en: "Floating notifications. Wrap the app in ToastProvider, then call toast({message, tone}) from useToast(). Auto-dismiss after 4s."
579
+ },
580
+ code: `import { ToastProvider, useToast } from "@mangtre/ui";
581
+
582
+ function Save() {
583
+ const { toast } = useToast();
584
+ return <button onClick={() => toast({ message: "\u0110\xE3 l\u01B0u \u2713", tone: "success" })}>L\u01B0u</button>;
585
+ }
586
+ // <ToastProvider><App/></ToastProvider>`
587
+ },
588
+ // ---- feedback ----
589
+ {
590
+ id: "feedback/states",
591
+ area: "feedback",
592
+ name: "EmptyState, ErrorState",
593
+ importPath: "@mangtre/ui",
594
+ status: "stable",
595
+ since: "P0",
596
+ version: "0.1.0",
597
+ summary: {
598
+ vi: "Tr\u1EA1ng th\xE1i r\u1ED7ng/l\u1ED7i c\u0103n gi\u1EEFa: icon + ti\xEAu \u0111\u1EC1 + m\xF4 t\u1EA3 + action. \u0110\u1EB7t trong Card ho\u1EB7c v\xF9ng n\u1ED9i dung.",
599
+ en: "Centered empty/error states: icon + title + description + action. Place inside a Card or content area."
600
+ },
601
+ code: `import { EmptyState, Button } from "@mangtre/ui";
602
+
603
+ <EmptyState icon="\u{1F331}" title="Ch\u01B0a c\xF3 nh\xF3m n\xE0o"
604
+ description="T\u1EA1o nh\xF3m \u0111\u1EA7u ti\xEAn \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u."
605
+ action={<Button size="sm">T\u1EA1o nh\xF3m</Button>} />`
606
+ },
607
+ {
608
+ id: "feedback/loaders",
609
+ area: "feedback",
610
+ name: "Skeleton, Spinner, Progress",
611
+ importPath: "@mangtre/ui",
612
+ status: "stable",
613
+ since: "P0",
614
+ version: "0.1.0",
615
+ summary: {
616
+ vi: "Ch\u1EC9 b\xE1o t\u1EA3i: Skeleton (khung ch\u1EDD), Spinner (xoay), Progress (thanh; b\u1ECF value = v\xF4 \u0111\u1ECBnh). An to\xE0n reduced-motion.",
617
+ en: "Loading indicators: Skeleton (placeholder), Spinner, Progress (bar; omit value = indeterminate). Reduced-motion safe."
618
+ },
619
+ code: `import { Skeleton, Spinner, Progress } from "@mangtre/ui";
620
+
621
+ <Spinner />
622
+ <Skeleton width="8rem" />
623
+ <Progress value={64} />
624
+ <Progress /> {/* indeterminate */}`
625
+ }
626
+ ];
627
+
628
+ // src/catalog/areas/domain.ts
629
+ var DOMAIN_ENTRIES = [
630
+ // ---- money ----
631
+ {
632
+ id: "money/components",
633
+ area: "money",
634
+ name: "AmountSummary, ExpenseList, ExpenseRow, SettlementCard, PaidBadge, UnpaidBadge",
635
+ importPath: "@mangtre/ui/money",
636
+ status: "stable",
637
+ since: "P1",
638
+ version: "0.1.0",
639
+ summary: {
640
+ vi: "B\u1ED9 chia ti\u1EC1n: t\u1ED5ng s\u1ED1 ti\u1EC1n l\u1EDBn, danh s\xE1ch/h\xE0ng kho\u1EA3n chi, th\u1EBB t\u1EA5t to\xE1n (k\xE8m slot VietQR + \u0111\xE1nh d\u1EA5u \u0111\xE3 tr\u1EA3), badge \u0111\xE3/ch\u01B0a tr\u1EA3. Hi\u1EC3n th\u1ECB thu\u1EA7n \u2014 kh\xF4ng gi\u1EEF ti\u1EC1n.",
641
+ en: "Money-splitting kit: big amount summary, expense list/rows, settlement card (with a VietQR slot + mark-paid), paid/unpaid badges. Display only \u2014 never holds money."
642
+ },
643
+ code: `import { AmountSummary, ExpenseList, ExpenseRow, SettlementCard } from "@mangtre/ui/money";
644
+
645
+ <AmountSummary label="T\u1ED5ng qu\u1EF9" amount={5900000} />
646
+ <ExpenseList>
647
+ <ExpenseRow note="\u0102n t\u1ED1i" meta="rum tr\u1EA3 \xB7 chia 3" amount={450000} />
648
+ </ExpenseList>
649
+ <SettlementCard from="B\xECnh" to="rum" amount={150000} onMarkPaid={pay} />`
650
+ },
651
+ // ---- learning ----
652
+ {
653
+ id: "learning/components",
654
+ area: "learning",
655
+ name: "Flashcard, QuizCard, ScoreCard, StreakBadge",
656
+ importPath: "@mangtre/ui/learning",
657
+ status: "stable",
658
+ since: "P1",
659
+ version: "0.1.0",
660
+ summary: {
661
+ vi: "B\u1ED9 h\u1ECDc t\u1EADp: th\u1EBB l\u1EADt (Flashcard), c\xE2u h\u1ECFi tr\u1EAFc nghi\u1EC7m (QuizCard, c\xF3 reveal \u0111\xFAng/sai), th\u1EBB \u0111i\u1EC3m, badge chu\u1ED7i ng\xE0y. App s\u1EDF h\u1EEFu logic SM-2/quiz.",
662
+ en: "Learning kit: flip Flashcard, multiple-choice QuizCard (with correct/wrong reveal), ScoreCard, StreakBadge. App owns the SM-2/quiz logic."
663
+ },
664
+ code: `import { Flashcard, QuizCard, ScoreCard, StreakBadge } from "@mangtre/ui/learning";
665
+
666
+ <Flashcard front={<>Photosynthesis?</>} back={<>Quang h\u1EE3p</>} />
667
+ <QuizCard question="Th\u1EE7 \u0111\xF4 VN?" options={[{id:"a",label:"H\xE0 N\u1ED9i"},{id:"b",label:"TP.HCM"}]}
668
+ value={pick} onSelect={setPick} />
669
+ <ScoreCard score={8} total={10} /> <StreakBadge days={12} />`
670
+ },
671
+ // ---- layout ----
672
+ {
673
+ id: "layout/components",
674
+ area: "layout",
675
+ name: "AppFrame, Page, Container, PageHeader, Section, BottomActionBar, SafeArea",
676
+ importPath: "@mangtre/ui/layout",
677
+ status: "stable",
678
+ since: "P2",
679
+ version: "0.1.0",
680
+ summary: {
681
+ vi: "Khung trang desktop-responsive (Design System \xA79): Page (gi\u1EDBi h\u1EA1n ~68rem), Container (\u0111\u1ECDc/cap), PageHeader (eyebrow+title+actions), Section, thanh h\xE0nh \u0111\u1ED9ng \u0111\xE1y d\xEDnh, SafeArea cho notch.",
682
+ en: "Desktop-responsive page scaffolding (Design System \xA79): Page (~68rem cap), Container, PageHeader (eyebrow+title+actions), Section, sticky BottomActionBar, SafeArea for notches."
683
+ },
684
+ code: `import { Page, PageHeader, Section, Button } from "@mangtre/ui/layout";
685
+
686
+ <Page>
687
+ <PageHeader eyebrow="Nh\xF3m" title="Chia ti\u1EC1n nh\xF3m" actions={<Button>Chia s\u1EBB</Button>} />
688
+ <Section title="Kho\u1EA3n chi">\u2026</Section>
689
+ </Page>`
690
+ },
691
+ {
692
+ id: "layout/ListRow",
693
+ area: "layout",
694
+ name: "ListRow",
695
+ importPath: "@mangtre/ui/layout",
696
+ status: "stable",
697
+ since: "P2",
698
+ version: "0.1.0",
699
+ summary: {
700
+ vi: "D\xF2ng danh s\xE1ch v\u1EDBi slot leading / title+meta / trailing. Th\xE0nh n\xFAt khi c\xF3 onClick. Ti\xEAu \u0111\u1EC1 t\u1EF1 c\u1EAFt (ellipsis); d\xF9ng cho m\u1ECDi list/menu (m\xF4n h\u1ECDc, deadline, th\xE0nh vi\xEAn\u2026).",
701
+ en: "A list item with leading / title+meta / trailing slots. Becomes a button with onClick. Title ellipsizes; for any list/menu (subjects, deadlines, members\u2026)."
702
+ },
703
+ code: `import { ListRow } from "@mangtre/ui/layout";
704
+
705
+ <ListRow
706
+ leading="\u{1F4D5}"
707
+ title="Gi\u1EA3i t\xEDch 1"
708
+ meta="T2 \xB7 7:00\u20139:00 \xB7 P.301"
709
+ trailing={<Badge>S\u1EAFp t\u1EDBi</Badge>}
710
+ onClick={() => open(slot)}
711
+ />`,
712
+ props: [
713
+ {
714
+ name: "title",
715
+ type: "ReactNode",
716
+ required: true,
717
+ desc: { vi: "D\xF2ng ti\xEAu \u0111\u1EC1", en: "Title line" }
718
+ },
719
+ {
720
+ name: "leading / trailing",
721
+ type: "ReactNode",
722
+ desc: { vi: "Slot tr\xE1i / ph\u1EA3i", en: "Left / right slots" }
723
+ },
724
+ {
725
+ name: "meta",
726
+ type: "ReactNode",
727
+ desc: { vi: "D\xF2ng ph\u1EE5 d\u01B0\u1EDBi ti\xEAu \u0111\u1EC1", en: "Secondary line" }
728
+ },
729
+ {
730
+ name: "onClick",
731
+ type: "() => void",
732
+ desc: { vi: "Bi\u1EBFn c\u1EA3 d\xF2ng th\xE0nh n\xFAt", en: "Make the whole row a button" }
733
+ }
734
+ ]
735
+ },
736
+ // ---- media ----
737
+ {
738
+ id: "media/components",
739
+ area: "media",
740
+ name: "Dropzone, FilePreview, ImagePreview, Avatar, AvatarGroup",
741
+ importPath: "@mangtre/ui/media",
742
+ status: "stable",
743
+ since: "P2",
744
+ version: "0.1.0",
745
+ summary: {
746
+ vi: "B\u1ED9 media: v\xF9ng k\xE9o-th\u1EA3 t\u1EC7p, th\u1EBB xem t\u1EC7p, xem \u1EA3nh, avatar (ch\u1EEF c\xE1i/\u1EA3nh) + nh\xF3m avatar ch\u1ED3ng. App x\u1EED l\xFD t\u1EC7p th\u1EADt.",
747
+ en: "Media kit: file dropzone, file preview card, image preview, Avatar (initials/image) + stacked AvatarGroup. App handles the actual files."
748
+ },
749
+ code: `import { Dropzone, Avatar, AvatarGroup } from "@mangtre/ui/media";
750
+
751
+ <Dropzone onFiles={(files) => upload(files)} accept="image/*" multiple />
752
+ <AvatarGroup people={[{ name: "An" }, { name: "B\xECnh" }]} />`
753
+ },
754
+ // ---- settings ----
755
+ {
756
+ id: "settings/components",
757
+ area: "settings",
758
+ name: "SettingsSection, SettingsRow, SettingsToggle",
759
+ importPath: "@mangtre/ui/settings",
760
+ status: "stable",
761
+ since: "P2",
762
+ version: "0.1.0",
763
+ summary: {
764
+ vi: "Khung trang c\xE0i \u0111\u1EB7t: nh\xF3m c\xF3 ti\xEAu \u0111\u1EC1, h\xE0ng (label+m\xF4 t\u1EA3+control), v\xE0 h\xE0ng c\xF4ng t\u1EAFc ti\u1EC7n d\u1EE5ng.",
765
+ en: "Settings scaffolding: titled section, row (label+description+control), and a convenience toggle row."
766
+ },
767
+ code: `import { SettingsSection, SettingsToggle } from "@mangtre/ui/settings";
768
+
769
+ <SettingsSection title="C\xE0i \u0111\u1EB7t">
770
+ <SettingsToggle label="Th\xF4ng b\xE1o" description="Nh\u1EAFc deadline h\u1EB1ng ng\xE0y"
771
+ checked={on} onChange={setOn} />
772
+ </SettingsSection>`
773
+ },
774
+ // ---- privacy ----
775
+ {
776
+ id: "privacy/components",
777
+ area: "privacy",
778
+ name: "PrivacyNotice, LocalFirstNotice, NoPiiBadge, ExternalLinkWarning",
779
+ importPath: "@mangtre/ui/privacy",
780
+ status: "stable",
781
+ since: "P2",
782
+ version: "0.1.0",
783
+ summary: {
784
+ vi: "Copy ni\u1EC1m tin/ri\xEAng t\u01B0: th\xF4ng b\xE1o chung, 'ch\u1EA1y offline \xB7 d\u1EEF li\u1EC7u tr\xEAn m\xE1y', badge kh\xF4ng-PII, c\u1EA3nh b\xE1o m\u1EDF li\xEAn k\u1EBFt ngo\xE0i. C\u1EE7ng c\u1ED1 h\u1EE3p \u0111\u1ED3ng local-first.",
785
+ en: "Trust/privacy copy: generic notice, 'runs offline \xB7 data on device', No-PII badge, external-link warning. Reinforces the local-first contract."
786
+ },
787
+ code: `import { LocalFirstNotice, NoPiiBadge } from "@mangtre/ui/privacy";
788
+
789
+ <LocalFirstNotice />
790
+ <NoPiiBadge />`
791
+ },
792
+ // ---- editor ----
793
+ {
794
+ id: "editor/components",
795
+ area: "editor",
796
+ name: "Prose, CodeBlock, Kbd, CalloutBlock",
797
+ importPath: "@mangtre/ui/editor",
798
+ status: "stable",
799
+ since: "P2",
800
+ version: "0.1.0",
801
+ summary: {
802
+ vi: "Nguy\xEAn th\u1EE7y n\u1ED9i dung: Prose (b\u1ECDc ch\u1EEF typographic), CodeBlock (c\xF3 n\xFAt ch\xE9p), Kbd (ph\xEDm), CalloutBlock (note/tip/warn/danger). KH\xD4NG nh\xFAng HTML th\xF4 \u2014 render markdown \u1EDF app sau khi sanitize.",
803
+ en: "Content primitives: Prose (typographic wrapper), CodeBlock (copy button), Kbd, CalloutBlock (note/tip/warn/danger). NO raw HTML injection \u2014 render markdown app-side after sanitizing."
804
+ },
805
+ code: `import { CodeBlock, CalloutBlock, Kbd } from "@mangtre/ui/editor";
806
+
807
+ <CalloutBlock tone="tip">D\xF9ng <Kbd>\u2318K</Kbd> \u0111\u1EC3 m\u1EDF b\u1EA3ng l\u1EC7nh.</CalloutBlock>
808
+ <CodeBlock code={'sdk.data?.set("k", { v: 1 })'} />`
809
+ },
810
+ // ---- monetization ----
811
+ {
812
+ id: "monetization/components",
813
+ area: "monetization",
814
+ name: "ComingSoonMonetizationCard, PayoutStatusBadge, EarningsLedgerTable",
815
+ importPath: "@mangtre/ui/monetization",
816
+ status: "stub",
817
+ since: "P2",
818
+ version: "0.1.0",
819
+ summary: {
820
+ vi: "CH\u1EC8 l\xE0 v\u1ECF tr\xECnh b\xE0y \u2014 thanh to\xE1n/chia doanh thu v\u1EABn n\u1EB1m ngo\xE0i l\u1ED9 tr\xECnh (LATER). D\xF9ng \u0111\u1EC3 dashboard hi\u1EC7n 's\u1EAFp c\xF3' m\xE0 kh\xF4ng ph\u1EA3i t\u1EF1 b\u1ECBa UI.",
821
+ en: "PRESENTATIONAL STUBS ONLY \u2014 payments/rev-share remain anti-roadmap (LATER). Lets a dashboard show a 'coming later' surface without inventing one."
822
+ },
823
+ code: `import { ComingSoonMonetizationCard } from "@mangtre/ui/monetization";
824
+
825
+ <ComingSoonMonetizationCard />`
826
+ }
827
+ ];
828
+
829
+ // src/catalog/areas/platform.ts
830
+ var PLATFORM_ENTRIES = [
831
+ // ---- platform ----
832
+ {
833
+ id: "platform/Badge",
834
+ area: "platform",
835
+ name: "Badge, VerifiedBadge",
836
+ importPath: "@mangtre/ui",
837
+ status: "stable",
838
+ since: "P0",
839
+ version: "0.1.0",
840
+ summary: {
841
+ vi: "Badge: vi\xEAn tr\u1EA1ng th\xE1i (tone accent/neutral/success/danger/warning/info). VerifiedBadge: d\u1EA5u 'M\u0103ng Verified' (compact = ch\u1EC9 icon).",
842
+ en: "Badge: status pill (tone accent/neutral/success/danger/warning/info). VerifiedBadge: the 'M\u0103ng Verified' mark (compact = icon only)."
843
+ },
844
+ code: `import { Badge, VerifiedBadge } from "@mangtre/ui";
845
+
846
+ <Badge tone="success">Th\xE0nh c\xF4ng</Badge>
847
+ <VerifiedBadge />`,
848
+ props: [
849
+ {
850
+ name: "tone",
851
+ type: '"accent" | "neutral" | "success" | "danger" | "warning" | "info"',
852
+ default: '"accent"',
853
+ desc: { vi: "S\u1EAFc th\xE1i", en: "Tone" }
854
+ }
855
+ ]
856
+ },
857
+ {
858
+ id: "platform/seam-badges",
859
+ area: "platform",
860
+ name: "SurfaceBadge, PermissionBadge, LocalBadge, CloudBadge, RealtimeBadge",
861
+ importPath: "@mangtre/ui",
862
+ status: "stable",
863
+ since: "P0",
864
+ version: "0.1.0",
865
+ summary: {
866
+ vi: "Badge g\u1EAFn seam: b\u1EC1 m\u1EB7t (desktop/web/zalo), quy\u1EC1n (sensitive \u2192 c\u1EA3nh b\xE1o), v\xE0 ch\u1EBF \u0111\u1ED9 d\u1EEF li\u1EC7u (Tr\xEAn m\xE1y / \u0110\xE1m m\xE2y / Tr\u1EF1c ti\u1EBFp).",
867
+ en: "Seam-aware badges: surface (desktop/web/zalo), permission (sensitive \u2192 warning), and data mode (On-device / Cloud / Live)."
868
+ },
869
+ code: `import { SurfaceBadge, PermissionBadge, CloudBadge } from "@mangtre/ui";
870
+
871
+ <SurfaceBadge surface="desktop" />
872
+ <PermissionBadge permission="data" sensitive />
873
+ <CloudBadge />`
874
+ },
875
+ {
876
+ id: "platform/gates",
877
+ area: "platform",
878
+ name: "SurfaceGate, PermissionGate, GracefulDegradePanel, DesktopOnly, WebOnly, ZaloOnly",
879
+ importPath: "@mangtre/ui",
880
+ status: "stable",
881
+ since: "P1",
882
+ version: "0.1.0",
883
+ summary: {
884
+ vi: "Hi\u1EC7n n\u1ED9i dung theo n\u01A1i ch\u1EA1y/quy\u1EC1n. B\u1ECB ch\u1EB7n \u2192 fallback (m\u1EB7c \u0111\u1ECBnh GracefulDegradePanel gi\u1EA3i th\xEDch, kh\xF4ng \u1EA9n l\u1EB7ng). Kh\u1EDBp seam surfaces.",
885
+ en: "Render by surface/permission. Blocked \u2192 fallback (default GracefulDegradePanel explains; never silently hidden). Matches the surfaces seam."
886
+ },
887
+ code: `import { SurfaceGate, PermissionGate } from "@mangtre/ui";
888
+
889
+ <SurfaceGate surfaces={["desktop"]} current={surface}>
890
+ <AiPanel />
891
+ </SurfaceGate>
892
+ <PermissionGate granted={canData} fallback={<p>C\u1EA7n quy\u1EC1n data</p>}>
893
+ <LiveRoom />
894
+ </PermissionGate>`
895
+ },
896
+ {
897
+ id: "platform/money-text",
898
+ area: "platform",
899
+ name: "VndText, MoneyText, formatVnd",
900
+ importPath: "@mangtre/ui",
901
+ status: "stable",
902
+ since: "P0",
903
+ version: "0.1.0",
904
+ summary: {
905
+ vi: "Hi\u1EC3n th\u1ECB ti\u1EC1n: VndText (\u0111\u1ED3ng VN, c\xF3 t\xF4 m\xE0u +/-), MoneyText (\u0111a ti\u1EC1n t\u1EC7), formatVnd (h\xE0m thu\u1EA7n). Ch\u1EC9 hi\u1EC3n th\u1ECB \u2014 M\u0103ng kh\xF4ng gi\u1EEF ti\u1EC1n.",
906
+ en: "Money display: VndText (\u0111\u1ED3ng, optional +/- color), MoneyText (any currency), formatVnd (pure fn). Display only \u2014 M\u0103ng never holds money."
907
+ },
908
+ code: `import { VndText, formatVnd } from "@mangtre/ui";
909
+
910
+ <VndText amount={150000} sign="auto" />
911
+ formatVnd(1500000); // "1.500.000 \u0111"`
912
+ },
913
+ // ---- charts ----
914
+ {
915
+ id: "charts/Stat",
916
+ area: "charts",
917
+ name: "Stat, StatGroup",
918
+ importPath: "@mangtre/ui/charts",
919
+ status: "stable",
920
+ since: "P1",
921
+ version: "0.1.0",
922
+ summary: {
923
+ vi: "Ch\u1EC9 s\u1ED1 \u0111\u01A1n (nh\xE3n + gi\xE1 tr\u1ECB + delta t\u0103ng/gi\u1EA3m) v\xE0 l\u01B0\u1EDBi Stat. Cho dashboard/b\xE1o c\xE1o.",
924
+ en: "Single metric (label + value + up/down delta) and a grid of Stats. For dashboards/reports."
925
+ },
926
+ code: `import { Stat, StatGroup } from "@mangtre/ui/charts";
927
+
928
+ <StatGroup>
929
+ <Stat label="Ng\u01B0\u1EDDi d\xF9ng" value="1.240" delta="+12%" direction="up" />
930
+ <Stat label="Quay l\u1EA1i" value="58%" delta="-3%" direction="down" />
931
+ </StatGroup>`
932
+ },
933
+ {
934
+ id: "charts/plots",
935
+ area: "charts",
936
+ name: "Sparkline, BarChart, LineChart, AreaChart, DonutChart, Heatmap, Legend",
937
+ importPath: "@mangtre/ui/charts",
938
+ status: "stable",
939
+ since: "P1",
940
+ version: "0.1.0",
941
+ summary: {
942
+ vi: "Bi\u1EC3u \u0111\u1ED3 SVG nh\u1EB9 (KH\xD4NG th\u01B0 vi\u1EC7n). M\xE0u chu\u1ED7i xoay theo palette th\u01B0\u01A1ng hi\u1EC7u; geometry thu\u1EA7n (chart-utils) c\xF3 test.",
943
+ en: "Lightweight inline-SVG charts (NO dependency). Series colors cycle the brand palette; pure geometry (chart-utils) is tested."
944
+ },
945
+ code: `import { LineChart, BarChart, DonutChart, Sparkline } from "@mangtre/ui/charts";
946
+
947
+ <Sparkline values={[3,7,4,9,6,11,8]} />
948
+ <LineChart values={[2,5,3,8,6,10,7]} area />
949
+ <BarChart values={[4,8,3,9,6]} />
950
+ <DonutChart values={[5,3,2]} />`,
951
+ props: [
952
+ { name: "values", type: "number[]", required: true, desc: { vi: "D\u1EEF li\u1EC7u", en: "Data" } }
953
+ ]
954
+ },
955
+ // ---- ai ----
956
+ {
957
+ id: "ai/PowerPicker",
958
+ area: "ai",
959
+ name: "PowerPicker",
960
+ importPath: "@mangtre/ui",
961
+ status: "stable",
962
+ since: "P0",
963
+ version: "0.1.0",
964
+ summary: {
965
+ vi: "B\u1ED9 ch\u1ECDn 'N\u0103ng l\u1EF1c AI' chu\u1EA9n cho seam ai. App truy\u1EC1n powers + value/onChange; component s\u1EDF h\u1EEFu UI (kh\xF4ng tu\u1EF3 bi\u1EBFn). \u1EA8n khi < 2 power.",
966
+ en: "The canonical 'AI power' picker for the ai seam. App passes powers + value/onChange; the component owns the UI (no per-app restyle). Hidden when < 2 powers."
967
+ },
968
+ code: `import { PowerPicker } from "@mangtre/ui";
969
+
970
+ <PowerPicker powers={powers} value={tier} onChange={setTier} locale="vi" />`
971
+ },
972
+ {
973
+ id: "ai/cards",
974
+ area: "ai",
975
+ name: "AiStatusCard, AiSetupCard",
976
+ importPath: "@mangtre/ui/ai",
977
+ status: "stable",
978
+ since: "P1",
979
+ version: "0.1.0",
980
+ summary: {
981
+ vi: "Th\u1EBB tr\u1EA1ng th\xE1i AI (s\u1EB5n s\xE0ng + d\xF2ng ri\xEAng t\u01B0) v\xE0 th\u1EBB c\xE0i \u0111\u1EB7t (c\xE0i runtime + t\u1EA3i model). App n\u1ED1i sdk.ai; component ch\u1EC9 tr\xECnh b\xE0y. Desktop-local.",
982
+ en: "AI status card (ready + privacy line) and setup card (install runtime + pull model). App wires sdk.ai; UI only. Desktop-local."
983
+ },
984
+ code: `import { AiStatusCard, AiSetupCard } from "@mangtre/ui/ai";
985
+
986
+ <AiStatusCard ready detail="qwen3 \xB7 balanced" />
987
+ <AiSetupCard models={[{ id: "q3", name: "Qwen3 3B", meta: "~2GB" }]} onInstall={...} onPull={...} />`
988
+ },
989
+ // ---- handoff ----
990
+ {
991
+ id: "handoff/components",
992
+ area: "handoff",
993
+ name: "HandoffPairingPanel, HandoffPayloadBanner, HandoffConnectionStatus",
994
+ importPath: "@mangtre/ui/handoff",
995
+ status: "stable",
996
+ since: "P1",
997
+ version: "0.1.0",
998
+ summary: {
999
+ vi: "UI cho seam handoff (d\xE1n t\u1EEB thi\u1EBFt b\u1ECB g\u1EA7n qua LAN, desktop-only). Panel gh\xE9p QR + tr\u1EA1ng th\xE1i; banner payload \u0111\u1EBFn \u2192 D\xE1n/B\u1ECF. App s\u1EDF h\u1EEFu transport + QR.",
1000
+ en: "UI for the handoff seam (paste from a nearby device over LAN, desktop-only). Pairing panel (QR + status); incoming-payload banner \u2192 Paste/Dismiss. App owns transport + QR."
1001
+ },
1002
+ code: `import { HandoffPairingPanel, HandoffPayloadBanner } from "@mangtre/ui/handoff";
1003
+
1004
+ <HandoffPairingPanel qr={<QrCanvas/>} connected device="iPhone" />
1005
+ <HandoffPayloadBanner kind="JSON" device="iPhone" onPaste={paste} onDismiss={hide} />`
1006
+ },
1007
+ // ---- data-room ----
1008
+ {
1009
+ id: "data-room/components",
1010
+ area: "data-room",
1011
+ name: "RoomStatusBadge, RoomShareButton, RoomLinkCard, RoomPresence, RoomConflictNotice",
1012
+ importPath: "@mangtre/ui/data-room",
1013
+ status: "stable",
1014
+ since: "P1",
1015
+ version: "0.1.0",
1016
+ summary: {
1017
+ vi: "UI cho seam data ('Ph\xF2ng s\u1ED1ng'): badge tr\u1EA1ng th\xE1i (offline/cloud/realtime + s\u1ED1 ng\u01B0\u1EDDi), n\xFAt chia s\u1EBB, th\u1EBB link, avatar hi\u1EC7n di\u1EC7n, th\xF4ng b\xE1o xung \u0111\u1ED9t. Capability-based, kh\xF4ng t\xE0i kho\u1EA3n.",
1018
+ en: "UI for the data seam ('Ph\xF2ng s\u1ED1ng'): status badge (offline/cloud/realtime + peers), share button, link card, presence avatars, conflict notice. Capability-based, no accounts."
1019
+ },
1020
+ code: `import { RoomStatusBadge, RoomPresence, RoomShareButton } from "@mangtre/ui/data-room";
1021
+
1022
+ <RoomStatusBadge status="realtime" peers={3} />
1023
+ <RoomPresence peers={["An","B\xECnh","Chi"]} />
1024
+ <RoomShareButton onShare={share} />`
1025
+ },
1026
+ // ---- analytics ----
1027
+ {
1028
+ id: "analytics/components",
1029
+ area: "analytics",
1030
+ name: "AnalyticsConsentToggle, GateMetBadge, GateReport, EventLogTable",
1031
+ importPath: "@mangtre/ui/analytics",
1032
+ status: "stable",
1033
+ since: "P1",
1034
+ version: "0.1.0",
1035
+ summary: {
1036
+ vi: "UI cho seam analytics: c\xF4ng t\u1EAFc \u0111\u1ED3ng \xFD (opt-out), badge \u0111\u1EA1t c\u1ED5ng, b\u1EA3ng retention m\u1ED7i-app, b\u1EA3ng log s\u1EF1 ki\u1EC7n. Non-PII theo h\u1EE3p \u0111\u1ED3ng.",
1037
+ en: "UI for the analytics seam: consent toggle (opt-out), gate-met badge, per-app retention table, event-log table. Non-PII by contract."
1038
+ },
1039
+ code: `import { AnalyticsConsentToggle, GateReport } from "@mangtre/ui/analytics";
1040
+
1041
+ <AnalyticsConsentToggle enabled={on} onChange={setOn} />
1042
+ <GateReport rows={[{ app: "chia-tien-nhom", devices: 120, returning: 64 }]} />`
1043
+ },
1044
+ // ---- sandbox ----
1045
+ {
1046
+ id: "sandbox/components",
1047
+ area: "sandbox",
1048
+ name: "SandboxFrame, SandboxError, SafetyScanBadge, StaticScanResult",
1049
+ importPath: "@mangtre/ui/sandbox",
1050
+ status: "stable",
1051
+ since: "P1",
1052
+ version: "0.1.0",
1053
+ summary: {
1054
+ vi: "UI h\u1ED9p c\xE1t/ki\u1EC3m duy\u1EC7t: khung quanh iframe app (badge ngu\u1ED3n/origin), tr\u1EA1ng th\xE1i l\u1ED7i, badge m\u1EE9c qu\xE9t (an to\xE0n/c\u1EA7n xem/ch\u1EB7n), danh s\xE1ch ph\xE1t hi\u1EC7n.",
1055
+ en: "Sandbox/curation UI: chrome around the app iframe (source/origin badges), error state, scan-level badge (safe/review/blocked), findings list."
1056
+ },
1057
+ code: `import { SandboxFrame, SafetyScanBadge } from "@mangtre/ui/sandbox";
1058
+
1059
+ <SandboxFrame origin="mang-apps.com" source={<Badge tone="neutral">sandbox</Badge>}>
1060
+ <iframe \u2026 />
1061
+ </SandboxFrame>
1062
+ <SafetyScanBadge level="review" />`
1063
+ },
1064
+ // ---- creator ----
1065
+ {
1066
+ id: "creator/components",
1067
+ area: "creator",
1068
+ name: "BundleUploadDropzone, ManifestPreview, StaticScanPanel, ReviewStatusBadge, VersionBadge, BuildBadge",
1069
+ importPath: "@mangtre/ui/creator",
1070
+ status: "stable",
1071
+ since: "P1",
1072
+ version: "0.1.0",
1073
+ summary: {
1074
+ vi: "UI b\u1EA3ng creator: k\xE9o-th\u1EA3 bundle, xem manifest, panel qu\xE9t t\u0129nh, badge tr\u1EA1ng th\xE1i duy\u1EC7t (ch\u1EDD/live/c\u1EA7n s\u1EEDa/t\u1EEB ch\u1ED1i), badge version/build.",
1075
+ en: "Creator-dashboard UI: bundle dropzone, manifest preview, static-scan panel, review-status badge (pending/live/changes/rejected), version/build badges."
1076
+ },
1077
+ code: `import { BundleUploadDropzone, ManifestPreview, ReviewStatusBadge } from "@mangtre/ui/creator";
1078
+
1079
+ <BundleUploadDropzone onFile={upload} file={selected} />
1080
+ <ManifestPreview manifest={{ id: "my-app", name: "My App", version: "1.0.0", permissions: ["storage"] }} />
1081
+ <ReviewStatusBadge status="pending_review" />`
1082
+ }
1083
+ ];
1084
+
1085
+ // src/catalog/index.ts
1086
+ var CATALOG_VERSION = "0.1.0";
1087
+ var CATALOG = [...CORE_ENTRIES, ...PLATFORM_ENTRIES, ...DOMAIN_ENTRIES];
1088
+ var AREAS = [
1089
+ {
1090
+ id: "theme",
1091
+ title: { vi: "Theme & n\u1EC1n", en: "Theme & foundation" },
1092
+ blurb: { vi: "Provider + hook + token", en: "Provider + hooks + tokens" }
1093
+ },
1094
+ {
1095
+ id: "primitives",
1096
+ title: { vi: "Nguy\xEAn th\u1EE7y", en: "Primitives" },
1097
+ blurb: { vi: "Layout + ch\u1EEF", en: "Layout + text" }
1098
+ },
1099
+ { id: "actions", title: { vi: "H\xE0nh \u0111\u1ED9ng", en: "Actions" }, blurb: { vi: "N\xFAt", en: "Buttons" } },
1100
+ {
1101
+ id: "forms",
1102
+ title: { vi: "Bi\u1EC3u m\u1EABu", en: "Forms" },
1103
+ blurb: { vi: "Field + control", en: "Field + controls" }
1104
+ },
1105
+ {
1106
+ id: "surface",
1107
+ title: { vi: "B\u1EC1 m\u1EB7t", en: "Surface" },
1108
+ blurb: { vi: "Card + Panel", en: "Cards + panels" }
1109
+ },
1110
+ {
1111
+ id: "navigation",
1112
+ title: { vi: "\u0110i\u1EC1u h\u01B0\u1EDBng", en: "Navigation" },
1113
+ blurb: { vi: "Tabs", en: "Tabs" }
1114
+ },
1115
+ {
1116
+ id: "overlay",
1117
+ title: { vi: "L\u1EDBp ph\u1EE7", en: "Overlay" },
1118
+ blurb: { vi: "Dialog/Sheet/Toast", en: "Dialog/Sheet/Toast" }
1119
+ },
1120
+ {
1121
+ id: "feedback",
1122
+ title: { vi: "Ph\u1EA3n h\u1ED3i", en: "Feedback" },
1123
+ blurb: { vi: "Empty/Error/Loader", en: "Empty/Error/loaders" }
1124
+ },
1125
+ {
1126
+ id: "platform",
1127
+ title: { vi: "N\u1EC1n t\u1EA3ng", en: "Platform" },
1128
+ blurb: { vi: "Badge + gate + ti\u1EC1n", en: "Badges + gates + money" }
1129
+ },
1130
+ {
1131
+ id: "charts",
1132
+ title: { vi: "Bi\u1EC3u \u0111\u1ED3", en: "Charts" },
1133
+ blurb: { vi: "SVG nh\u1EB9", en: "Lightweight SVG" }
1134
+ },
1135
+ { id: "ai", title: { vi: "AI", en: "AI" }, blurb: { vi: "Seam ai", en: "ai seam" } },
1136
+ {
1137
+ id: "handoff",
1138
+ title: { vi: "Handoff", en: "Handoff" },
1139
+ blurb: { vi: "Seam handoff (LAN)", en: "handoff seam (LAN)" }
1140
+ },
1141
+ {
1142
+ id: "data-room",
1143
+ title: { vi: "Ph\xF2ng s\u1ED1ng", en: "Data room" },
1144
+ blurb: { vi: "Seam data", en: "data seam" }
1145
+ },
1146
+ {
1147
+ id: "analytics",
1148
+ title: { vi: "Ph\xE2n t\xEDch", en: "Analytics" },
1149
+ blurb: { vi: "Seam analytics", en: "analytics seam" }
1150
+ },
1151
+ {
1152
+ id: "sandbox",
1153
+ title: { vi: "H\u1ED9p c\xE1t", en: "Sandbox" },
1154
+ blurb: { vi: "Ki\u1EC3m duy\u1EC7t/an to\xE0n", en: "Curation/safety" }
1155
+ },
1156
+ {
1157
+ id: "creator",
1158
+ title: { vi: "Ng\u01B0\u1EDDi t\u1EA1o", en: "Creator" },
1159
+ blurb: { vi: "\u0110\u0103ng app", en: "Publishing" }
1160
+ },
1161
+ { id: "money", title: { vi: "Ti\u1EC1n", en: "Money" }, blurb: { vi: "Chia ti\u1EC1n", en: "Splitting" } },
1162
+ {
1163
+ id: "learning",
1164
+ title: { vi: "H\u1ECDc t\u1EADp", en: "Learning" },
1165
+ blurb: { vi: "Flashcard/quiz", en: "Flashcard/quiz" }
1166
+ },
1167
+ {
1168
+ id: "layout",
1169
+ title: { vi: "B\u1ED1 c\u1EE5c", en: "Layout" },
1170
+ blurb: { vi: "Khung trang", en: "Page scaffolding" }
1171
+ },
1172
+ {
1173
+ id: "media",
1174
+ title: { vi: "Media", en: "Media" },
1175
+ blurb: { vi: "T\u1EC7p/\u1EA3nh/avatar", en: "Files/images/avatars" }
1176
+ },
1177
+ {
1178
+ id: "settings",
1179
+ title: { vi: "C\xE0i \u0111\u1EB7t", en: "Settings" },
1180
+ blurb: { vi: "Trang c\xE0i \u0111\u1EB7t", en: "Settings rows" }
1181
+ },
1182
+ {
1183
+ id: "privacy",
1184
+ title: { vi: "Ri\xEAng t\u01B0", en: "Privacy" },
1185
+ blurb: { vi: "Copy ni\u1EC1m tin", en: "Trust copy" }
1186
+ },
1187
+ {
1188
+ id: "editor",
1189
+ title: { vi: "So\u1EA1n th\u1EA3o", en: "Editor" },
1190
+ blurb: { vi: "Prose/CodeBlock", en: "Prose/CodeBlock" }
1191
+ },
1192
+ {
1193
+ id: "monetization",
1194
+ title: { vi: "Ki\u1EBFm ti\u1EC1n", en: "Monetization" },
1195
+ blurb: { vi: "V\u1ECF (LATER)", en: "Stubs (LATER)" }
1196
+ }
1197
+ ];
1198
+ function entriesByArea(area) {
1199
+ return CATALOG.filter((e) => e.area === area);
1200
+ }
1201
+ function getEntry(id) {
1202
+ return CATALOG.find((e) => e.id === id);
1203
+ }
1204
+ export {
1205
+ AREAS,
1206
+ CATALOG,
1207
+ CATALOG_VERSION,
1208
+ entriesByArea,
1209
+ getEntry
1210
+ };