@butinapp/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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/dist/components/badge.d.ts +7 -0
  4. package/dist/components/badge.js +34 -0
  5. package/dist/components/button.d.ts +11 -0
  6. package/dist/components/button.js +30 -0
  7. package/dist/components/card.d.ts +8 -0
  8. package/dist/components/card.js +10 -0
  9. package/dist/components/checkbox.d.ts +3 -0
  10. package/dist/components/checkbox.js +7 -0
  11. package/dist/components/combobox.d.ts +14 -0
  12. package/dist/components/combobox.js +53 -0
  13. package/dist/components/data-table-model.d.ts +40 -0
  14. package/dist/components/data-table-model.js +105 -0
  15. package/dist/components/data-table.d.ts +33 -0
  16. package/dist/components/data-table.js +252 -0
  17. package/dist/components/dialog.d.ts +11 -0
  18. package/dist/components/dialog.js +15 -0
  19. package/dist/components/dropdown-menu.d.ts +7 -0
  20. package/dist/components/dropdown-menu.js +15 -0
  21. package/dist/components/echart.d.ts +5 -0
  22. package/dist/components/echart.js +27 -0
  23. package/dist/components/input.d.ts +3 -0
  24. package/dist/components/input.js +5 -0
  25. package/dist/components/label.d.ts +3 -0
  26. package/dist/components/label.js +5 -0
  27. package/dist/components/progress.d.ts +4 -0
  28. package/dist/components/progress.js +8 -0
  29. package/dist/components/select.d.ts +11 -0
  30. package/dist/components/select.js +5 -0
  31. package/dist/components/separator.d.ts +3 -0
  32. package/dist/components/separator.js +5 -0
  33. package/dist/components/service-icon.d.ts +19 -0
  34. package/dist/components/service-icon.js +49 -0
  35. package/dist/components/sheet.d.ts +10 -0
  36. package/dist/components/sheet.js +11 -0
  37. package/dist/components/skeleton.d.ts +2 -0
  38. package/dist/components/skeleton.js +4 -0
  39. package/dist/components/sparkline.d.ts +7 -0
  40. package/dist/components/sparkline.js +22 -0
  41. package/dist/components/switch.d.ts +3 -0
  42. package/dist/components/switch.js +5 -0
  43. package/dist/components/tabs.d.ts +8 -0
  44. package/dist/components/tabs.js +15 -0
  45. package/dist/components/tooltip.d.ts +6 -0
  46. package/dist/components/tooltip.js +8 -0
  47. package/dist/components/truncated-cell.d.ts +3 -0
  48. package/dist/components/truncated-cell.js +22 -0
  49. package/dist/dashboard.d.ts +12 -0
  50. package/dist/dashboard.js +13 -0
  51. package/dist/features/charts.d.ts +7 -0
  52. package/dist/features/charts.js +4 -0
  53. package/dist/features/dashboard/dashboard-renderer.d.ts +32 -0
  54. package/dist/features/dashboard/dashboard-renderer.js +322 -0
  55. package/dist/features/dashboard/format-role.d.ts +2 -0
  56. package/dist/features/dashboard/format-role.js +26 -0
  57. package/dist/features/dashboard/overview-model.d.ts +59 -0
  58. package/dist/features/dashboard/overview-model.js +170 -0
  59. package/dist/features/dashboard/overview.d.ts +8 -0
  60. package/dist/features/dashboard/overview.js +136 -0
  61. package/dist/features/dashboard/plan-views.d.ts +7 -0
  62. package/dist/features/dashboard/plan-views.js +29 -0
  63. package/dist/features/dashboard/rollup.d.ts +31 -0
  64. package/dist/features/dashboard/rollup.js +45 -0
  65. package/dist/features/dashboard/timeseries-chart.d.ts +11 -0
  66. package/dist/features/dashboard/timeseries-chart.js +191 -0
  67. package/dist/features/dashboard/view-models.d.ts +72 -0
  68. package/dist/features/dashboard/view-models.js +291 -0
  69. package/dist/features/documents/doc-models.d.ts +1 -0
  70. package/dist/features/documents/doc-models.js +17 -0
  71. package/dist/features/documents/open-file-button.d.ts +6 -0
  72. package/dist/features/documents/open-file-button.js +16 -0
  73. package/dist/features/shell/app-shell.d.ts +13 -0
  74. package/dist/features/shell/app-shell.js +22 -0
  75. package/dist/features/shell/conn-state.d.ts +10 -0
  76. package/dist/features/shell/conn-state.js +29 -0
  77. package/dist/features/shell/service-tabs.d.ts +13 -0
  78. package/dist/features/shell/service-tabs.js +20 -0
  79. package/dist/features/shell/sidebar.d.ts +31 -0
  80. package/dist/features/shell/sidebar.js +31 -0
  81. package/dist/features/shell/theme-toggle.d.ts +5 -0
  82. package/dist/features/shell/theme-toggle.js +12 -0
  83. package/dist/i18n/context.d.ts +16 -0
  84. package/dist/i18n/context.js +19 -0
  85. package/dist/i18n/format-context.d.ts +7 -0
  86. package/dist/i18n/format-context.js +6 -0
  87. package/dist/i18n/format.d.ts +10 -0
  88. package/dist/i18n/format.js +31 -0
  89. package/dist/i18n/index.d.ts +9 -0
  90. package/dist/i18n/index.js +8 -0
  91. package/dist/i18n/labels.d.ts +400 -0
  92. package/dist/i18n/labels.js +828 -0
  93. package/dist/i18n/plugin-text.d.ts +4 -0
  94. package/dist/i18n/plugin-text.js +150 -0
  95. package/dist/index.d.ts +6 -0
  96. package/dist/index.js +15 -0
  97. package/dist/lib/echarts-theme.d.ts +38 -0
  98. package/dist/lib/echarts-theme.js +59 -0
  99. package/dist/lib/format.d.ts +4 -0
  100. package/dist/lib/format.js +18 -0
  101. package/dist/lib/monthly-buckets.d.ts +9 -0
  102. package/dist/lib/monthly-buckets.js +32 -0
  103. package/dist/lib/utils.d.ts +2 -0
  104. package/dist/lib/utils.js +3 -0
  105. package/dist/primitives.d.ts +28 -0
  106. package/dist/primitives.js +26 -0
  107. package/dist/shell.d.ts +8 -0
  108. package/dist/shell.js +10 -0
  109. package/dist/theme.css +137 -0
  110. package/dist/types.d.ts +11 -0
  111. package/dist/types.js +6 -0
  112. package/package.json +78 -0
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import {} from '../../i18n/index.js';
4
+ import { cn } from '../../primitives.js';
5
+ export const connDotClass = {
6
+ connected: 'bg-emerald-500',
7
+ unverified: 'bg-sky-500',
8
+ disconnected: 'bg-destructive'
9
+ };
10
+ export const connLabel = (t, state) => state === 'connected' ? t.statusConnected : state === 'unverified' ? t.statusUnverified : t.statusDisconnected;
11
+ // The status dot, drawn one way everywhere. `className` tunes size/placement per surface (smaller in the
12
+ // header pill, nudged with ml-auto in the sidebar); `title` carries the hover label where there's no text.
13
+ // `testing` is the shared in-flight flag (one service can show in several places at once): while true the dot
14
+ // pulses with an expanding ping ring, and whenever the resolved state CHANGES it plays a one-shot ping so a
15
+ // finished probe visibly "lands" wherever the service is drawn instead of silently recoloring.
16
+ export const ConnDot = ({ state, testing, className, title }) => {
17
+ const [flash, setFlash] = useState(false);
18
+ const prev = useRef(state);
19
+ useEffect(() => {
20
+ if (prev.current === state) {
21
+ return;
22
+ }
23
+ prev.current = state;
24
+ setFlash(true);
25
+ const id = setTimeout(() => setFlash(false), 600);
26
+ return () => clearTimeout(id);
27
+ }, [state]);
28
+ return (_jsxs("span", { className: cn('relative inline-flex size-2', className), title: title, children: [testing || flash ? (_jsx("span", { className: cn('absolute inline-flex size-full animate-ping rounded-full opacity-75', connDotClass[state]) })) : null, _jsx("span", { className: cn('relative size-full rounded-full', connDotClass[state], testing && 'animate-pulse') })] }));
29
+ };
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type CapabilityView } from '../../types.js';
3
+ export interface ServiceTab {
4
+ capability: CapabilityView;
5
+ body: ReactNode;
6
+ trailing?: boolean;
7
+ icon?: ReactNode;
8
+ }
9
+ export declare const ServiceTabs: ({ tabs, active, onSelect }: {
10
+ tabs: ServiceTab[];
11
+ active: string;
12
+ onSelect: (capabilityId: string) => void;
13
+ }) => import("react").JSX.Element | null;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLabels } from '../../i18n/index.js';
3
+ import { cn, Tabs, TabsList, TabsTrigger } from '../../primitives.js';
4
+ import {} from '../../types.js';
5
+ // The per-service tab bar (one tab per capability) over the active tab's body. `value`/`onValueChange`
6
+ // are controlled by the host so the active tab maps to the URL. We render only the active body (not a
7
+ // TabsContent per tab) so inactive tabs never mount — that's what makes per-tab lazy loading work.
8
+ // The body wrapper is KEYED by the active capability id so switching tabs remounts a fresh body instead
9
+ // of React reusing one instance across tabs (same component type at the same position). Without the key,
10
+ // a tab's in-component state — a just-run refresh result, fetch/auto-run refs — bleeds into the next tab,
11
+ // so the highlight moves but the content below keeps showing the previous tab's data.
12
+ export const ServiceTabs = ({ tabs, active, onSelect }) => {
13
+ const t = useLabels();
14
+ const current = tabs.find((tab) => tab.capability.id === active) ?? tabs[0];
15
+ if (!current) {
16
+ return null;
17
+ }
18
+ const firstTrailingId = tabs.find((tab) => tab.trailing)?.capability.id;
19
+ return (_jsxs(Tabs, { value: current.capability.id, onValueChange: onSelect, children: [_jsx(TabsList, { variant: "line", children: tabs.map((tab) => (_jsxs(TabsTrigger, { value: tab.capability.id, className: cn(tab.capability.id === firstTrailingId && 'ml-auto'), children: [tab.icon, t.s(tab.capability.label)] }, tab.capability.id))) }), _jsx("div", { className: "pt-3", children: current.body }, current.capability.id)] }));
20
+ };
@@ -0,0 +1,31 @@
1
+ import { type ConnState } from './conn-state.js';
2
+ export type SidebarDot = ConnState;
3
+ export interface SidebarService {
4
+ id: string;
5
+ name: string;
6
+ color?: string;
7
+ icon?: string;
8
+ dot?: SidebarDot;
9
+ testing?: boolean;
10
+ disabled?: boolean;
11
+ }
12
+ export type SidebarTarget = {
13
+ kind: 'overview';
14
+ } | {
15
+ kind: 'management';
16
+ } | {
17
+ kind: 'developer';
18
+ } | {
19
+ kind: 'service';
20
+ serviceId: string;
21
+ };
22
+ export type SidebarActive = SidebarTarget;
23
+ export declare const Sidebar: ({ services, active, onSelect, onNavigate, onOpenSettings, showManagement, showDeveloper }: {
24
+ services: SidebarService[];
25
+ active: SidebarActive;
26
+ onSelect: (target: SidebarTarget) => void;
27
+ onNavigate?: () => void;
28
+ onOpenSettings?: () => void;
29
+ showManagement?: boolean;
30
+ showDeveloper?: boolean;
31
+ }) => import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Database, LayoutGrid, Search, Settings, Wrench } from 'lucide-react';
3
+ import { useState } from 'react';
4
+ import { useLabels } from '../../i18n/index.js';
5
+ import { cn, Input, ServiceIcon } from '../../primitives.js';
6
+ import { ConnDot } from './conn-state.js';
7
+ const rowClass = (active) => cn('flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors', active
8
+ ? 'bg-secondary text-secondary-foreground'
9
+ : 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground');
10
+ // The fixed left rail: Management link, then a searchable service list with Overview pinned first. Each
11
+ // service shows its brand-color icon and a connection-confidence dot (when it has data). Pure +
12
+ // prop-driven; the host owns navigation. `onNavigate` lets a mobile sheet close itself after a pick.
13
+ export const Sidebar = ({ services, active, onSelect, onNavigate, onOpenSettings, showManagement = true, showDeveloper = true }) => {
14
+ const t = useLabels();
15
+ const [query, setQuery] = useState('');
16
+ const filtered = services.filter((s) => s.name.toLowerCase().includes(query.trim().toLowerCase()));
17
+ const go = (target) => {
18
+ onSelect(target);
19
+ onNavigate?.();
20
+ };
21
+ return (_jsxs("aside", { className: "bg-sidebar text-sidebar-foreground flex w-56 shrink-0 flex-col gap-3 border-r p-3", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-muted-foreground px-2 text-[10px] font-medium tracking-wider uppercase", children: t.servicesHeading }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "text-muted-foreground pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: t.searchPlaceholder, className: "h-8 pl-7 text-xs" })] })] }), _jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto", children: [_jsxs("button", { className: rowClass(active.kind === 'overview'), onClick: () => go({ kind: 'overview' }), children: [_jsx(LayoutGrid, { className: "size-4 shrink-0" }), t.navOverview] }), filtered.length === 0 ? (_jsx("p", { className: "text-muted-foreground px-2 py-1.5 text-xs", children: t.noServicesMatch })) : (filtered.map((s) => {
22
+ const isActive = active.kind === 'service' && active.serviceId === s.id;
23
+ if (s.disabled) {
24
+ return (_jsxs("div", { title: t.sidebarDisabledHint, "aria-disabled": true, className: cn(rowClass(false), 'cursor-not-allowed opacity-40 hover:bg-transparent'), children: [_jsx(ServiceIcon, { id: s.id, icon: s.icon, name: s.name, color: s.color, size: 16 }), _jsx("span", { className: "truncate", children: s.name })] }, s.id));
25
+ }
26
+ return (_jsxs("button", { className: rowClass(isActive), onClick: () => go({ kind: 'service', serviceId: s.id }), children: [_jsx(ServiceIcon, { id: s.id, icon: s.icon, name: s.name, color: s.color, size: 16 }), _jsx("span", { className: "truncate", children: s.name }), s.dot ? (_jsx(ConnDot, { state: s.dot, testing: s.testing, className: "ml-auto size-1.5 shrink-0", title: s.dot })) : null] }, s.id));
27
+ }))] }), showManagement || showDeveloper || onOpenSettings ? (_jsxs("div", { className: "space-y-0.5 border-t pt-2", children: [_jsx("div", { className: "text-muted-foreground px-2 pb-1 text-[10px] font-medium tracking-wider uppercase", children: t.systemHeading }), showManagement ? (_jsxs("button", { className: rowClass(active.kind === 'management'), onClick: () => go({ kind: 'management' }), children: [_jsx(Database, { className: "size-4 shrink-0" }), t.navManagement] })) : null, showDeveloper ? (_jsxs("button", { className: rowClass(active.kind === 'developer'), onClick: () => go({ kind: 'developer' }), children: [_jsx(Wrench, { className: "size-4 shrink-0" }), t.navDeveloper] })) : null, onOpenSettings ? (_jsxs("button", { className: rowClass(false), onClick: () => {
28
+ onOpenSettings();
29
+ onNavigate?.();
30
+ }, children: [_jsx(Settings, { className: "size-4 shrink-0" }), t.navSettings] })) : null] })) : null] }));
31
+ };
@@ -0,0 +1,5 @@
1
+ export declare const ThemeToggle: ({ isDark, onToggle, label }: {
2
+ isDark: boolean;
3
+ onToggle: () => void;
4
+ label?: string;
5
+ }) => import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Moon, Sun } from 'lucide-react';
3
+ import { useLabels } from '../../i18n/index.js';
4
+ import { Button } from '../../primitives.js';
5
+ // A compact light/dark switch for the top bar. Pure + prop-driven — the host owns theme state (@butinapp/ui
6
+ // ships no theme state); this renders the current mode and emits a toggle, showing the icon of the mode it
7
+ // switches TO. The label falls back to the i18n contract so an embed needn't pass one.
8
+ export const ThemeToggle = ({ isDark, onToggle, label }) => {
9
+ const t = useLabels();
10
+ const text = label ?? t.themeToggle;
11
+ return (_jsx(Button, { variant: "ghost", size: "icon", onClick: onToggle, "aria-label": text, title: text, children: isDark ? _jsx(Sun, { className: "size-4" }) : _jsx(Moon, { className: "size-4" }) }));
12
+ };
@@ -0,0 +1,16 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type ButinLabels, type Locale } from './labels.js';
3
+ import { type PluginMessages } from './plugin-text.js';
4
+ export type ButinI18n = ButinLabels & {
5
+ s: (text: string) => string;
6
+ };
7
+ export declare const useLabels: () => ButinI18n;
8
+ export declare const I18nProvider: ({ locale, messages, children }: {
9
+ locale: Locale;
10
+ messages?: PluginMessages;
11
+ children: ReactNode;
12
+ }) => import("react").JSX.Element;
13
+ export declare const LabelsProvider: ({ value, children }: {
14
+ value: ButinLabels;
15
+ children: ReactNode;
16
+ }) => import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from 'react';
3
+ import { butinLabels, en } from './labels.js';
4
+ import { makePluginText } from './plugin-text.js';
5
+ // Defaults to English with an identity `s`, so a component (or an embed) rendered without a provider still
6
+ // works. The host wraps the app with the active locale; a service page re-wraps with that plugin's
7
+ // `meta.messages` merged in.
8
+ const LabelsContext = createContext({ ...en, s: (text) => text });
9
+ export const useLabels = () => useContext(LabelsContext);
10
+ // The unified provider: the active locale's chrome dict + a plugin-string translator built from that locale
11
+ // (and optional per-plugin `messages` merged over the global dict). Nest it on a service page to layer in a
12
+ // plugin's domain vocabulary; the inner provider replaces `s` while keeping the same chrome dict.
13
+ export const I18nProvider = ({ locale, messages, children }) => {
14
+ const value = useMemo(() => ({ ...butinLabels[locale], s: makePluginText(locale, messages) }), [locale, messages]);
15
+ return _jsx(LabelsContext.Provider, { value: value, children: children });
16
+ };
17
+ // Entry point for tests/embeds that supply a raw chrome dict directly (no locale). Plugin strings pass
18
+ // through unchanged (identity `s`) — use I18nProvider when plugin-string translation is needed.
19
+ export const LabelsProvider = ({ value, children }) => (_jsx(LabelsContext.Provider, { value: { ...value, s: (text) => text }, children: children }));
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type FormatPrefs } from './format.js';
3
+ export declare const FormatProvider: ({ value, children }: {
4
+ value: FormatPrefs;
5
+ children: ReactNode;
6
+ }) => import("react").JSX.Element;
7
+ export declare const useFormat: () => FormatPrefs;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ import { DEFAULT_FORMAT_PREFS } from './format.js';
4
+ const FormatContext = createContext(DEFAULT_FORMAT_PREFS);
5
+ export const FormatProvider = ({ value, children }) => (_jsx(FormatContext.Provider, { value: value, children: children }));
6
+ export const useFormat = () => useContext(FormatContext);
@@ -0,0 +1,10 @@
1
+ export type CurrencyStyle = 'match' | 'us' | 'fr' | 'eu';
2
+ export type DateFormatPreset = 'locale' | 'iso' | 'us' | 'eu';
3
+ export interface FormatPrefs {
4
+ currencyStyle: CurrencyStyle;
5
+ dateFormat: DateFormatPreset;
6
+ }
7
+ export declare const DEFAULT_FORMAT_PREFS: FormatPrefs;
8
+ export declare const resolveNumberLocale: (style: CurrencyStyle, fallbackLocale: string) => string;
9
+ export declare const formatDateTime: (value: string | number | Date, prefs: FormatPrefs, fallbackLocale: string) => string;
10
+ export declare const formatRelative: (then: Date, now: Date, locale: string) => string;
@@ -0,0 +1,31 @@
1
+ // Pure, node-testable display-format helpers. The currency style overrides the NUMBER-formatting locale
2
+ // only (grouping/symbol placement) — never the amount, never the currency code (no FX). Date helpers apply
3
+ // a preset to app-generated dates (meta lines), in the machine's local zone, not to raw plugin-emitted cells.
4
+ import { DateTime } from 'luxon';
5
+ export const DEFAULT_FORMAT_PREFS = { currencyStyle: 'match', dateFormat: 'locale' };
6
+ const NUMBER_LOCALES = {
7
+ us: 'en-US',
8
+ fr: 'fr-CA',
9
+ eu: 'de-DE'
10
+ };
11
+ // 'match' follows the app's intlLocale; the named presets pin a region's number/currency formatting.
12
+ export const resolveNumberLocale = (style, fallbackLocale) => style === 'match' ? fallbackLocale : NUMBER_LOCALES[style];
13
+ // An ISO-look 'YYYY-MM-DD HH:mm' in the machine's local zone (a plain toISOString can't — it's always UTC).
14
+ // Year→minute fields for the meta-line display.
15
+ const isoLocal = (date) => DateTime.fromJSDate(date).toFormat('yyyy-MM-dd HH:mm');
16
+ export const formatDateTime = (value, prefs, fallbackLocale) => {
17
+ if (value === '' || value == null) {
18
+ return '—';
19
+ }
20
+ const date = value instanceof Date ? value : new Date(value);
21
+ if (Number.isNaN(date.getTime())) {
22
+ return String(value);
23
+ }
24
+ if (prefs.dateFormat === 'iso') {
25
+ return isoLocal(date);
26
+ }
27
+ const locale = prefs.dateFormat === 'us' ? 'en-US' : prefs.dateFormat === 'eu' ? 'en-GB' : fallbackLocale;
28
+ return date.toLocaleString(locale, { dateStyle: 'medium', timeStyle: 'short' });
29
+ };
30
+ // Largest unit whose magnitude is >= 1 (down to seconds), localized. Wording comes from the locale.
31
+ export const formatRelative = (then, now, locale) => DateTime.fromJSDate(then).toRelative({ base: DateTime.fromJSDate(now), locale }) ?? '';
@@ -0,0 +1,9 @@
1
+ export { I18nProvider, LabelsProvider, useLabels } from './context.js';
2
+ export type { ButinI18n } from './context.js';
3
+ export { FormatProvider, useFormat } from './format-context.js';
4
+ export { DEFAULT_FORMAT_PREFS, resolveNumberLocale, formatDateTime, formatRelative } from './format.js';
5
+ export type { FormatPrefs, CurrencyStyle, DateFormatPreset } from './format.js';
6
+ export { en, fr, butinLabels } from './labels.js';
7
+ export type { ButinLabels, Locale } from './labels.js';
8
+ export { makePluginText, globalPluginText } from './plugin-text.js';
9
+ export type { PluginMessages } from './plugin-text.js';
@@ -0,0 +1,8 @@
1
+ // @butinapp/ui/i18n — the label + number/date formatting contract: the shape, an English default (context
2
+ // falls back to it, so embeds need no provider), the French dict, and the host providers/hooks. Cross-cutting
3
+ // (both /dashboard and /shell read it), so it stands as its own layer.
4
+ export { I18nProvider, LabelsProvider, useLabels } from './context.js';
5
+ export { FormatProvider, useFormat } from './format-context.js';
6
+ export { DEFAULT_FORMAT_PREFS, resolveNumberLocale, formatDateTime, formatRelative } from './format.js';
7
+ export { en, fr, butinLabels } from './labels.js';
8
+ export { makePluginText, globalPluginText } from './plugin-text.js';
@@ -0,0 +1,400 @@
1
+ import type { PluginCategory, TroubleshootingCause } from '@butinapp/sdk';
2
+ export type Locale = 'en' | 'fr';
3
+ export interface ButinLabels {
4
+ intlLocale: string;
5
+ headline: string;
6
+ tagline: string;
7
+ libraryTitle: string;
8
+ librarySubtitle: string;
9
+ dataHeading: string;
10
+ homeAria: string;
11
+ navManagement: string;
12
+ navOverview: string;
13
+ navDeveloper: string;
14
+ logsHeading: string;
15
+ navSettings: string;
16
+ devNavBrowser: string;
17
+ systemHeading: string;
18
+ servicesHeading: string;
19
+ searchPlaceholder: string;
20
+ noServicesMatch: string;
21
+ sidebarDisabledHint: string;
22
+ menuLabel: string;
23
+ tabInstalled: string;
24
+ tabAvailable: string;
25
+ installAction: string;
26
+ uninstallAction: string;
27
+ uninstallArmed: string;
28
+ uninstallConfirm: string;
29
+ uninstallEraseFolder: string;
30
+ uninstallEraseFolderDocs: (n: number) => string;
31
+ allInstalled: string;
32
+ availableSearchPlaceholder: string;
33
+ skipForNow: string;
34
+ finishSetup: string;
35
+ onboardingContinue: string;
36
+ onboardingSignInTitle: string;
37
+ onboardingSignInBlurb: string;
38
+ onboardingSettingsTitle: string;
39
+ onboardingSettingsBlurb: string;
40
+ onboardingRefreshTitle: string;
41
+ onboardingRefreshBlurb: string;
42
+ actionRetry: string;
43
+ actionReconnect: string;
44
+ actionEditSettings: string;
45
+ actionOpenDashboard: string;
46
+ actionOpenDocs: string;
47
+ errorDetails: string;
48
+ failureMessage: Record<TroubleshootingCause, string>;
49
+ exportButton: string;
50
+ exportTitle: string;
51
+ exportDescription: string;
52
+ exportServices: string;
53
+ exportSelectAll: string;
54
+ exportNoData: string;
55
+ exportEncrypt: string;
56
+ exportEncryptHint: string;
57
+ exportPlaintextWarning: string;
58
+ exportPlaintextNote: string;
59
+ exportDestination: string;
60
+ exportDestinationDefault: string;
61
+ exportChange: string;
62
+ exportRunning: string;
63
+ exportDone: string;
64
+ exportReveal: string;
65
+ sessionSaved: string;
66
+ sessionSecured: string;
67
+ sessionNotCaptured: string;
68
+ sessionCanceled: string;
69
+ sessionCleared: string;
70
+ settingsSaved: string;
71
+ dataSaved: (capability: string) => string;
72
+ fetchFailed: string;
73
+ settingsTitle: string;
74
+ settingsSubtitle: string;
75
+ themeLabel: string;
76
+ themeHint: string;
77
+ themeToggle: string;
78
+ sectionLanguage: string;
79
+ languageHint: string;
80
+ sectionDataPrivacy: string;
81
+ sectionAbout: string;
82
+ settingsNavGeneral: string;
83
+ settingsNavAdvanced: string;
84
+ settingsNavSystem: string;
85
+ settingsNavStorage: string;
86
+ settingsNavBrowserSignin: string;
87
+ browserSigninTitle: string;
88
+ browserSigninLead: string;
89
+ browserSigninStepsTitle: string;
90
+ browserSigninStep1: string;
91
+ browserSigninStep2: string;
92
+ browserSigninStep3: string;
93
+ browserSigninButton: string;
94
+ browserSigninReopen: string;
95
+ browserSigninConnected: string;
96
+ browserSigninNone: string;
97
+ browserSigninBusy: string;
98
+ browserSigninSynced: (n: number) => string;
99
+ browserSigninError: string;
100
+ browserSigninAdvanced: string;
101
+ browserSigninUrlLabel: string;
102
+ browserSigninPathLabel: string;
103
+ browserSigninPathHint: string;
104
+ browserSigninRemoveTitle: string;
105
+ browserSigninRemoveHint: string;
106
+ browserSigninRemove: string;
107
+ browserSigninRemoveTooltip: string;
108
+ settingsLoading: string;
109
+ settingsSaveFailed: string;
110
+ openFolderFailed: string;
111
+ startPageLabel: string;
112
+ startPageHint: string;
113
+ startPageOverview: string;
114
+ startPageLast: string;
115
+ sectionSecurity: string;
116
+ idleLockLabel: string;
117
+ idleLockHint: string;
118
+ idleLockOff: string;
119
+ lockOnSleepLabel: string;
120
+ lockOnSleepHint: string;
121
+ cacheWindowLabel: string;
122
+ cacheWindowHint: string;
123
+ logLevelLabel: string;
124
+ logLevelHint: string;
125
+ logRetentionLabel: string;
126
+ logRetentionHint: string;
127
+ manualCaptureLabel: string;
128
+ manualCaptureHint: string;
129
+ paceRequestsLabel: string;
130
+ paceRequestsHint: string;
131
+ devModeSection: string;
132
+ devModeLabel: string;
133
+ devModeHint: string;
134
+ sectionFormatting: string;
135
+ currencyFormatLabel: string;
136
+ currencyFormatHint: string;
137
+ dateFormatLabel: string;
138
+ dateFormatHint: string;
139
+ sectionCurrency: string;
140
+ baseCurrencyLabel: string;
141
+ baseCurrencyHint: string;
142
+ exchangeRatesLabel: string;
143
+ exchangeRatesHint: string;
144
+ exchangeRatesEmpty: string;
145
+ saveRates: string;
146
+ clearDataConfirm: string;
147
+ noServices: string;
148
+ sessionLive: string;
149
+ locked: string;
150
+ reportCount: (n: number) => string;
151
+ allServices: string;
152
+ sessionTitle: string;
153
+ sessionLiveBadge: string;
154
+ sessionStoredDesc: string;
155
+ sessionEmptyDesc: string;
156
+ capturingSession: string;
157
+ recaptureSession: string;
158
+ captureSession: string;
159
+ clearSession: string;
160
+ advancedPaste: string;
161
+ hide: string;
162
+ pastePlaceholder: string;
163
+ save: string;
164
+ fetch: string;
165
+ fetching: string;
166
+ completedAt: (when: string) => string;
167
+ refresh: string;
168
+ refreshThisTab: string;
169
+ refetchAll: string;
170
+ notLoadedYet: string;
171
+ refreshedAgo: (relative: string, absolute: string) => string;
172
+ refreshWarning: string;
173
+ connectPrompt: (service: string) => string;
174
+ noDataYet: string;
175
+ serviceCount: (n: number) => string;
176
+ overviewSpending: string;
177
+ overviewBalances: string;
178
+ overviewOthers: string;
179
+ netWorth: string;
180
+ itemCount: (n: number) => string;
181
+ lastFetched: (when: string) => string;
182
+ chartMonthly: string;
183
+ chartDaily: string;
184
+ chartYears: string;
185
+ chartAllYears: string;
186
+ chartEstimated: string;
187
+ chartMtdEstimate: string;
188
+ noRows: string;
189
+ tableSelectAll: (n: number) => string;
190
+ tableColumns: string;
191
+ tableExport: string;
192
+ tablePerPage: (n: number) => string;
193
+ tablePageOf: (page: number, total: number) => string;
194
+ tableRowCount: (n: number) => string;
195
+ tablePrev: string;
196
+ tableNext: string;
197
+ toggleRowDetails: string;
198
+ perDayHeading: string;
199
+ estimatedTag: string;
200
+ cellCopy: string;
201
+ cellCopied: string;
202
+ documentsEmpty: string;
203
+ documentsFolderLabel: string;
204
+ documentsChangeFolder: string;
205
+ downloadSelected: (n: number) => string;
206
+ downloadAll: string;
207
+ openFolder: string;
208
+ openDocument: string;
209
+ viewDocument: string;
210
+ retryFailed: string;
211
+ selectAll: string;
212
+ docDownloaded: string;
213
+ docDownloading: string;
214
+ docFailed: string;
215
+ downloadDone: (done: number, skipped: number) => string;
216
+ serviceSettingsTitle: string;
217
+ perServiceConfig: string;
218
+ saveSettings: string;
219
+ leaveBlank: string;
220
+ noServicesRegistered: string;
221
+ connect: string;
222
+ reconnect: string;
223
+ extractAll: string;
224
+ extractAllRunning: string;
225
+ extractAllDone: (tabs: number, files: number) => string;
226
+ test: string;
227
+ enabledLabel: string;
228
+ enableToUseHint: string;
229
+ refreshData: string;
230
+ extractAllHint: string;
231
+ filterAll: string;
232
+ filterEnabled: string;
233
+ filterConnected: string;
234
+ filterUnverified: string;
235
+ filterDisconnected: string;
236
+ filterIssues: string;
237
+ filterDisabled: string;
238
+ filterStale: string;
239
+ testAll: string;
240
+ refreshAll: string;
241
+ testAllCount: (n: number) => string;
242
+ refreshAllCount: (n: number) => string;
243
+ lastRefreshed: (rel: string) => string;
244
+ neverRefreshed: string;
245
+ dismissSummary: string;
246
+ testingProgress: (done: number, total: number) => string;
247
+ refreshingProgress: (done: number, total: number) => string;
248
+ category: Record<PluginCategory, string>;
249
+ serviceDisabledBanner: string;
250
+ refreshSummary: (refreshed: number, needsReconnect: number) => string;
251
+ needsReconnect: string;
252
+ settingsConnection: string;
253
+ settingsConfiguration: string;
254
+ settingsService: string;
255
+ settingsData: string;
256
+ openDashboard: string;
257
+ openInButin: string;
258
+ refreshAllTabs: string;
259
+ testPassed: string;
260
+ testFailed: string;
261
+ enabledHint: string;
262
+ statusConnected: string;
263
+ statusDisconnected: string;
264
+ statusUnverified: string;
265
+ disconnect: string;
266
+ eraseData: string;
267
+ eraseDataHint: string;
268
+ eraseDataDocsKept: (n: number) => string;
269
+ dataErased: string;
270
+ moveToProfile: string;
271
+ moveToProfileHint: string;
272
+ moveConfirmTo: (name: string) => string;
273
+ cancel: string;
274
+ moveDone: (name: string) => string;
275
+ outputFolder: string;
276
+ never: string;
277
+ statRecords: string;
278
+ statFiles: string;
279
+ statOnDisk: string;
280
+ statLastSynced: string;
281
+ invCapability: string;
282
+ invRecords: string;
283
+ invLastFetched: string;
284
+ invHistory: string;
285
+ invTrend: string;
286
+ invView: string;
287
+ settingsUnderHood: string;
288
+ underHoodSubtitle: string;
289
+ mechHowConnects: string;
290
+ mechAuth: string;
291
+ mechTransport: string;
292
+ mechCookieDomains: string;
293
+ mechSignIn: string;
294
+ mechPlugin: string;
295
+ requiresBrowserEngineLabel: string;
296
+ settingsLedgerDebug: string;
297
+ ledgerDebugSubtitle: string;
298
+ ledgerLoading: string;
299
+ ledgerNoData: string;
300
+ ledgerDatasetsTitle: string;
301
+ ledgerSeriesTitle: string;
302
+ ledgerColRow: string;
303
+ ledgerColFirstSeen: string;
304
+ ledgerColLastSeen: string;
305
+ ledgerColVersions: string;
306
+ ledgerRows: string;
307
+ ledgerPoints: string;
308
+ ledgerDerivedDaily: string;
309
+ settingsManage: string;
310
+ profileSwitchTo: (name: string) => string;
311
+ profileCurrent: string;
312
+ profileActionsAria: (name: string) => string;
313
+ profileRename: string;
314
+ profileDuplicate: string;
315
+ profileDuplicateLockedHint: string;
316
+ profileColor: string;
317
+ profileColorAria: (name: string) => string;
318
+ profileManageEncryption: string;
319
+ profileDelete: string;
320
+ profileDeleteConfirm: string;
321
+ profileNewName: string;
322
+ profileAdd: string;
323
+ profileSave: string;
324
+ profileActive: string;
325
+ profileNameAria: string;
326
+ profilesDialogTitle: string;
327
+ profilesDialogBlurb: string;
328
+ encStateOff: string;
329
+ encStateLocked: string;
330
+ encStateUnlocked: string;
331
+ encBadgeLockedAria: string;
332
+ encBadgeUnlockedAria: string;
333
+ encryptProfile: string;
334
+ encryptProfileHint: string;
335
+ encMasterPassword: string;
336
+ encConfirmPassword: string;
337
+ encPasswordMismatch: string;
338
+ encEnable: string;
339
+ encEnabling: string;
340
+ encRecoveryTitle: string;
341
+ encRecoveryBlurb: string;
342
+ encRecoveryKey: string;
343
+ encCopy: string;
344
+ encCopied: string;
345
+ encSavedRecovery: string;
346
+ encDone: string;
347
+ encLock: string;
348
+ encUnlockToManage: string;
349
+ encChangePassword: string;
350
+ encChangePasswordTitle: string;
351
+ encOldPassword: string;
352
+ encNewPassword: string;
353
+ encChangeSaved: string;
354
+ encWrongPassword: string;
355
+ encResetTitle: string;
356
+ encResetBlurb: string;
357
+ encReset: string;
358
+ encWrongRecovery: string;
359
+ encDisable: string;
360
+ encDisableTitle: string;
361
+ encDisableBlurb: string;
362
+ encDisableConfirm: string;
363
+ encDisabled: string;
364
+ encDuplicatePlaintext: string;
365
+ lockGateTitle: string;
366
+ lockGateBlurb: string;
367
+ lockGatePassword: string;
368
+ lockGateUnlock: string;
369
+ lockGateUnlocking: string;
370
+ lockGateUseRecovery: string;
371
+ lockGateUsePassword: string;
372
+ lockGateBadSecret: string;
373
+ lockGateSwitchProfile: string;
374
+ lockGateForgot: string;
375
+ notifications: {
376
+ title: string;
377
+ empty: string;
378
+ markAll: string;
379
+ windowDod: string;
380
+ windowWow: string;
381
+ windowMom: string;
382
+ facetSpend: string;
383
+ facetUsage: string;
384
+ allServices: string;
385
+ up: string;
386
+ down: string;
387
+ newActivity: string;
388
+ fxMissingTitle: string;
389
+ fxMissingBody: (currency: string, base: string, service: string) => string;
390
+ dismiss: string;
391
+ unread: (count: number) => string;
392
+ settingsChangeTitle: string;
393
+ settingsHealthTitle: string;
394
+ settingsFxMissing: string;
395
+ settingsHint: string;
396
+ };
397
+ }
398
+ export declare const en: ButinLabels;
399
+ export declare const fr: ButinLabels;
400
+ export declare const butinLabels: Record<Locale, ButinLabels>;