@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.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/components/badge.d.ts +7 -0
- package/dist/components/badge.js +34 -0
- package/dist/components/button.d.ts +11 -0
- package/dist/components/button.js +30 -0
- package/dist/components/card.d.ts +8 -0
- package/dist/components/card.js +10 -0
- package/dist/components/checkbox.d.ts +3 -0
- package/dist/components/checkbox.js +7 -0
- package/dist/components/combobox.d.ts +14 -0
- package/dist/components/combobox.js +53 -0
- package/dist/components/data-table-model.d.ts +40 -0
- package/dist/components/data-table-model.js +105 -0
- package/dist/components/data-table.d.ts +33 -0
- package/dist/components/data-table.js +252 -0
- package/dist/components/dialog.d.ts +11 -0
- package/dist/components/dialog.js +15 -0
- package/dist/components/dropdown-menu.d.ts +7 -0
- package/dist/components/dropdown-menu.js +15 -0
- package/dist/components/echart.d.ts +5 -0
- package/dist/components/echart.js +27 -0
- package/dist/components/input.d.ts +3 -0
- package/dist/components/input.js +5 -0
- package/dist/components/label.d.ts +3 -0
- package/dist/components/label.js +5 -0
- package/dist/components/progress.d.ts +4 -0
- package/dist/components/progress.js +8 -0
- package/dist/components/select.d.ts +11 -0
- package/dist/components/select.js +5 -0
- package/dist/components/separator.d.ts +3 -0
- package/dist/components/separator.js +5 -0
- package/dist/components/service-icon.d.ts +19 -0
- package/dist/components/service-icon.js +49 -0
- package/dist/components/sheet.d.ts +10 -0
- package/dist/components/sheet.js +11 -0
- package/dist/components/skeleton.d.ts +2 -0
- package/dist/components/skeleton.js +4 -0
- package/dist/components/sparkline.d.ts +7 -0
- package/dist/components/sparkline.js +22 -0
- package/dist/components/switch.d.ts +3 -0
- package/dist/components/switch.js +5 -0
- package/dist/components/tabs.d.ts +8 -0
- package/dist/components/tabs.js +15 -0
- package/dist/components/tooltip.d.ts +6 -0
- package/dist/components/tooltip.js +8 -0
- package/dist/components/truncated-cell.d.ts +3 -0
- package/dist/components/truncated-cell.js +22 -0
- package/dist/dashboard.d.ts +12 -0
- package/dist/dashboard.js +13 -0
- package/dist/features/charts.d.ts +7 -0
- package/dist/features/charts.js +4 -0
- package/dist/features/dashboard/dashboard-renderer.d.ts +32 -0
- package/dist/features/dashboard/dashboard-renderer.js +322 -0
- package/dist/features/dashboard/format-role.d.ts +2 -0
- package/dist/features/dashboard/format-role.js +26 -0
- package/dist/features/dashboard/overview-model.d.ts +59 -0
- package/dist/features/dashboard/overview-model.js +170 -0
- package/dist/features/dashboard/overview.d.ts +8 -0
- package/dist/features/dashboard/overview.js +136 -0
- package/dist/features/dashboard/plan-views.d.ts +7 -0
- package/dist/features/dashboard/plan-views.js +29 -0
- package/dist/features/dashboard/rollup.d.ts +31 -0
- package/dist/features/dashboard/rollup.js +45 -0
- package/dist/features/dashboard/timeseries-chart.d.ts +11 -0
- package/dist/features/dashboard/timeseries-chart.js +191 -0
- package/dist/features/dashboard/view-models.d.ts +72 -0
- package/dist/features/dashboard/view-models.js +291 -0
- package/dist/features/documents/doc-models.d.ts +1 -0
- package/dist/features/documents/doc-models.js +17 -0
- package/dist/features/documents/open-file-button.d.ts +6 -0
- package/dist/features/documents/open-file-button.js +16 -0
- package/dist/features/shell/app-shell.d.ts +13 -0
- package/dist/features/shell/app-shell.js +22 -0
- package/dist/features/shell/conn-state.d.ts +10 -0
- package/dist/features/shell/conn-state.js +29 -0
- package/dist/features/shell/service-tabs.d.ts +13 -0
- package/dist/features/shell/service-tabs.js +20 -0
- package/dist/features/shell/sidebar.d.ts +31 -0
- package/dist/features/shell/sidebar.js +31 -0
- package/dist/features/shell/theme-toggle.d.ts +5 -0
- package/dist/features/shell/theme-toggle.js +12 -0
- package/dist/i18n/context.d.ts +16 -0
- package/dist/i18n/context.js +19 -0
- package/dist/i18n/format-context.d.ts +7 -0
- package/dist/i18n/format-context.js +6 -0
- package/dist/i18n/format.d.ts +10 -0
- package/dist/i18n/format.js +31 -0
- package/dist/i18n/index.d.ts +9 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/labels.d.ts +400 -0
- package/dist/i18n/labels.js +828 -0
- package/dist/i18n/plugin-text.d.ts +4 -0
- package/dist/i18n/plugin-text.js +150 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +15 -0
- package/dist/lib/echarts-theme.d.ts +38 -0
- package/dist/lib/echarts-theme.js +59 -0
- package/dist/lib/format.d.ts +4 -0
- package/dist/lib/format.js +18 -0
- package/dist/lib/monthly-buckets.d.ts +9 -0
- package/dist/lib/monthly-buckets.js +32 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +3 -0
- package/dist/primitives.d.ts +28 -0
- package/dist/primitives.js +26 -0
- package/dist/shell.d.ts +8 -0
- package/dist/shell.js +10 -0
- package/dist/theme.css +137 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +6 -0
- package/package.json +78 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Locale } from './labels.js';
|
|
2
|
+
export type PluginMessages = Record<string, Record<string, string>>;
|
|
3
|
+
export declare const globalPluginText: PluginMessages;
|
|
4
|
+
export declare const makePluginText: (locale: Locale, plugin?: PluginMessages) => (text: string) => string;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// en→fr for the common, English-canonical vocabulary plugins + presets emit.
|
|
2
|
+
const EN_TO_FR = {
|
|
3
|
+
// Capability tabs / sections.
|
|
4
|
+
Summary: 'Résumé',
|
|
5
|
+
Billing: 'Facturation',
|
|
6
|
+
Usage: 'Utilisation',
|
|
7
|
+
Members: 'Membres',
|
|
8
|
+
Member: 'Membre',
|
|
9
|
+
'API Keys': 'Clés API',
|
|
10
|
+
'API keys': 'Clés API',
|
|
11
|
+
Documents: 'Documents',
|
|
12
|
+
Invoices: 'Factures',
|
|
13
|
+
Payments: 'Paiements',
|
|
14
|
+
Activity: 'Activité',
|
|
15
|
+
Account: 'Compte',
|
|
16
|
+
Authentication: 'Authentification',
|
|
17
|
+
Overview: 'Aperçu',
|
|
18
|
+
// Common column / field labels.
|
|
19
|
+
Name: 'Nom',
|
|
20
|
+
Email: 'Courriel',
|
|
21
|
+
Role: 'Rôle',
|
|
22
|
+
Status: 'Statut',
|
|
23
|
+
Amount: 'Montant',
|
|
24
|
+
Date: 'Date',
|
|
25
|
+
Time: 'Heure',
|
|
26
|
+
Month: 'Mois',
|
|
27
|
+
Type: 'Type',
|
|
28
|
+
Created: 'Créé',
|
|
29
|
+
'Created by': 'Créé par',
|
|
30
|
+
Expires: 'Expiration',
|
|
31
|
+
Expiration: 'Expiration',
|
|
32
|
+
Quantity: 'Quantité',
|
|
33
|
+
Region: 'Région',
|
|
34
|
+
Organization: 'Organisation',
|
|
35
|
+
'Organization ID': "ID d'organisation",
|
|
36
|
+
'Organization slug': "Identifiant d'organisation",
|
|
37
|
+
Project: 'Projet',
|
|
38
|
+
Cluster: 'Grappe',
|
|
39
|
+
Card: 'Carte',
|
|
40
|
+
'Last 4': '4 derniers',
|
|
41
|
+
Plan: 'Forfait',
|
|
42
|
+
Spend: 'Dépenses',
|
|
43
|
+
'Spend (MTD)': 'Dépenses (cumul mois)',
|
|
44
|
+
'This month': 'Ce mois-ci',
|
|
45
|
+
Credits: 'Crédits',
|
|
46
|
+
'Credit balance': 'Solde de crédits',
|
|
47
|
+
'Prepaid balance': 'Solde prépayé',
|
|
48
|
+
'Current charges': 'Frais actuels',
|
|
49
|
+
'Current cycle': 'Cycle actuel',
|
|
50
|
+
'Next charge': 'Prochain prélèvement',
|
|
51
|
+
'On-demand spend': 'Dépenses à la demande',
|
|
52
|
+
'Seats purchased': 'Sièges achetés',
|
|
53
|
+
'Seats used': 'Sièges utilisés',
|
|
54
|
+
'Team seats': "Sièges d'équipe",
|
|
55
|
+
Seat: 'Siège',
|
|
56
|
+
'Pending invites': 'Invitations en attente',
|
|
57
|
+
'Open invoices': 'Factures ouvertes',
|
|
58
|
+
'Payment status': 'Statut de paiement',
|
|
59
|
+
'Customer since': 'Client depuis',
|
|
60
|
+
Model: 'Modèle',
|
|
61
|
+
Service: 'Service',
|
|
62
|
+
Item: 'Article',
|
|
63
|
+
Product: 'Produit',
|
|
64
|
+
Username: "Nom d'utilisateur",
|
|
65
|
+
Country: 'Pays',
|
|
66
|
+
Address: 'Adresse',
|
|
67
|
+
Location: 'Emplacement',
|
|
68
|
+
Phone: 'Téléphone',
|
|
69
|
+
Identifier: 'Identifiant',
|
|
70
|
+
Key: 'Clé',
|
|
71
|
+
'Last used': 'Dernière utilisation',
|
|
72
|
+
Limit: 'Limite',
|
|
73
|
+
Used: 'Utilisé',
|
|
74
|
+
Unit: 'Unité',
|
|
75
|
+
Period: 'Période',
|
|
76
|
+
Metric: 'Métrique',
|
|
77
|
+
Metrics: 'Métriques',
|
|
78
|
+
Errors: 'Erreurs',
|
|
79
|
+
Cost: 'Coût',
|
|
80
|
+
Attachments: 'Pièces jointes',
|
|
81
|
+
Share: 'Part',
|
|
82
|
+
Utilization: 'Utilisation',
|
|
83
|
+
Stickiness: 'Fidélité',
|
|
84
|
+
Metered: 'Mesuré',
|
|
85
|
+
Size: 'Taille',
|
|
86
|
+
Max: 'Max',
|
|
87
|
+
'Daily active': 'Actifs/jour',
|
|
88
|
+
'Weekly active': 'Actifs/semaine',
|
|
89
|
+
'Monthly active': 'Actifs/mois',
|
|
90
|
+
'Used today': "Utilisé aujourd'hui",
|
|
91
|
+
'Used last month': 'Utilisé le mois dernier',
|
|
92
|
+
// AWS / cloud specifics.
|
|
93
|
+
'Access key ID': "ID de clé d'accès",
|
|
94
|
+
'Secret access key': "Clé d'accès secrète",
|
|
95
|
+
'IAM access keys': "Clés d'accès IAM",
|
|
96
|
+
'Identity Store ID': "ID du magasin d'identités",
|
|
97
|
+
'Identity Center users': 'Utilisateurs Identity Center',
|
|
98
|
+
'Profile name': 'Nom du profil',
|
|
99
|
+
'AWS profile': 'Profil AWS',
|
|
100
|
+
'Tax ID': 'Numéro fiscal',
|
|
101
|
+
'Billing email': 'Courriel de facturation',
|
|
102
|
+
'Billing name': 'Nom de facturation',
|
|
103
|
+
'Account name': 'Nom du compte',
|
|
104
|
+
'Account ID': 'ID du compte',
|
|
105
|
+
'Enterprise slug': "Identifiant d'entreprise",
|
|
106
|
+
'Team slug': "Identifiant d'équipe",
|
|
107
|
+
// View titles.
|
|
108
|
+
'Monthly spend': 'Dépenses mensuelles',
|
|
109
|
+
'Monthly meterings': 'Relevés mensuels',
|
|
110
|
+
'Daily spend': 'Dépenses quotidiennes',
|
|
111
|
+
'Daily credits': 'Crédits quotidiens',
|
|
112
|
+
'Payment method': 'Mode de paiement',
|
|
113
|
+
'This month by cluster': 'Ce mois par grappe',
|
|
114
|
+
'Spend by service': 'Dépenses par service',
|
|
115
|
+
'Spend by model': 'Dépenses par modèle',
|
|
116
|
+
'Spend by linked account': 'Dépenses par compte lié',
|
|
117
|
+
'Top projects (on-demand)': 'Meilleurs projets (à la demande)',
|
|
118
|
+
'Licensed items': 'Éléments sous licence'
|
|
119
|
+
};
|
|
120
|
+
// fr→en for the common French-canonical vocabulary (banking / health plugins), so they read in English
|
|
121
|
+
// under an English app without each shipping its own `messages`. Domain terms (DIN, RAMQ, posologie) stay
|
|
122
|
+
// per-plugin. Built by inverting EN_TO_FR plus a few French-first terms those plugins emit.
|
|
123
|
+
const FR_TO_EN = {
|
|
124
|
+
...Object.fromEntries(Object.entries(EN_TO_FR).map(([en, fr]) => [fr, en])),
|
|
125
|
+
Sommaire: 'Summary',
|
|
126
|
+
Comptes: 'Accounts',
|
|
127
|
+
Compte: 'Account',
|
|
128
|
+
Solde: 'Balance',
|
|
129
|
+
'Solde précédent': 'Previous balance',
|
|
130
|
+
Factures: 'Invoices',
|
|
131
|
+
Facture: 'Invoice',
|
|
132
|
+
Téléphone: 'Phone',
|
|
133
|
+
Adresse: 'Address',
|
|
134
|
+
Naissance: 'Date of birth',
|
|
135
|
+
Sexe: 'Sex',
|
|
136
|
+
Échéance: 'Due date',
|
|
137
|
+
'Montant dû': 'Amount due',
|
|
138
|
+
'Avoir net': 'Net worth',
|
|
139
|
+
Restante: 'Remaining',
|
|
140
|
+
Statut: 'Status',
|
|
141
|
+
Situation: 'Situation'
|
|
142
|
+
};
|
|
143
|
+
// Keyed by locale → (canonical-emitted-string → localized). `en`/`fr` only; other locales pass through.
|
|
144
|
+
export const globalPluginText = { en: FR_TO_EN, fr: EN_TO_FR };
|
|
145
|
+
// Build a translator for the active locale: plugin `messages` override the global dict; unknown strings pass
|
|
146
|
+
// through unchanged (so a service's data + untranslated bespoke labels render verbatim). Pure — unit-tested.
|
|
147
|
+
export const makePluginText = (locale, plugin) => (text) => {
|
|
148
|
+
const map = { ...(globalPluginText[locale] ?? {}), ...(plugin?.[locale] ?? {}) };
|
|
149
|
+
return map[text] ?? text;
|
|
150
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { PluginView, CapabilityView, ReportResult, VerifyResult, ConfigFieldView } from './types.js';
|
|
2
|
+
export type { ConfigOption } from '@butinapp/sdk';
|
|
3
|
+
export * from './primitives.js';
|
|
4
|
+
export * from './dashboard.js';
|
|
5
|
+
export * from './shell.js';
|
|
6
|
+
export * from './i18n/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @butinapp/ui — the embeddable, theme-portable design system + the generic data-view renderer. The surface
|
|
2
|
+
// is layered across subpaths so an import line says which tier it's in:
|
|
3
|
+
// @butinapp/ui/primitives — pure visual components (Button, Card, Table, …) + cn
|
|
4
|
+
// @butinapp/ui/dashboard — the descriptor-driven renderer + Overview + chart helpers (the embeddable layer)
|
|
5
|
+
// @butinapp/ui/shell — the embeddable shell scaffolding (AppShell · Sidebar · ServiceTabs · ThemeToggle)
|
|
6
|
+
// @butinapp/ui/i18n — the label + format contract
|
|
7
|
+
// The app's OWN chrome (profiles, vault, settings, notifications, export) is NOT here — it's
|
|
8
|
+
// app-specific, so it lives in core's renderer (core/src/renderer/chrome), leaving this package embeddable.
|
|
9
|
+
// This root barrel carries only the cross-cutting view-model types every layer shares, then re-aggregates the
|
|
10
|
+
// subpaths so existing root imports keep resolving. (The aggregate stays until the out-of-repo viewer — which
|
|
11
|
+
// still imports from the root — is migrated to subpaths alongside publishing; in-repo code imports the tiers.)
|
|
12
|
+
export * from './primitives.js';
|
|
13
|
+
export * from './dashboard.js';
|
|
14
|
+
export * from './shell.js';
|
|
15
|
+
export * from './i18n/index.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface EchartsTheme {
|
|
2
|
+
LABEL: {
|
|
3
|
+
color: string;
|
|
4
|
+
fontSize: number;
|
|
5
|
+
};
|
|
6
|
+
AXIS_LINE: {
|
|
7
|
+
axisLine: {
|
|
8
|
+
lineStyle: {
|
|
9
|
+
color: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
splitLine: {
|
|
13
|
+
lineStyle: {
|
|
14
|
+
color: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
TOOLTIP: {
|
|
19
|
+
trigger: 'axis';
|
|
20
|
+
backgroundColor: string;
|
|
21
|
+
borderColor: string;
|
|
22
|
+
textStyle: {
|
|
23
|
+
color: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
LEGEND_TEXT: string;
|
|
27
|
+
ACCENT: string;
|
|
28
|
+
PALETTE: string[];
|
|
29
|
+
}
|
|
30
|
+
export declare const grid: (top?: number) => {
|
|
31
|
+
left: number;
|
|
32
|
+
right: number;
|
|
33
|
+
top: number;
|
|
34
|
+
bottom: number;
|
|
35
|
+
containLabel: boolean;
|
|
36
|
+
};
|
|
37
|
+
export declare const asCanvasColor: (value: string) => string;
|
|
38
|
+
export declare const useEchartsTheme: () => EchartsTheme;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
// Chart grid. `top` defaults to 28 (room for a legend); pass 16 for charts without one.
|
|
3
|
+
export const grid = (top = 28) => ({ left: 8, right: 16, top, bottom: 8, containLabel: true });
|
|
4
|
+
// Tailwind v4 ships full color functions (`oklch(…)`) that are valid as-is, but a bare HSL triplet
|
|
5
|
+
// (`0 0% 26%`) is meant to be wrapped by the CSS. The canvas can't parse a bare triplet and silently
|
|
6
|
+
// drops the stroke. Wrap triplets into `hsl(…)`; pass real color functions through.
|
|
7
|
+
const HSL_TRIPLET = /^-?[\d.]+\s+[\d.]+%\s+[\d.]+%(\s*\/\s*[\d.]+%?)?$/;
|
|
8
|
+
export const asCanvasColor = (value) => (HSL_TRIPLET.test(value) ? `hsl(${value})` : value);
|
|
9
|
+
// Resolve a CSS custom property to a CONCRETE rgb() string. Reading the property directly yields its raw
|
|
10
|
+
// authored value (e.g. `oklch(0.235 0.01 275)`), which ECharts/zrender — and especially the DOM tooltip's
|
|
11
|
+
// background — can't reliably parse, so it silently falls back to its translucent default. We instead set
|
|
12
|
+
// `color: var(--token)` on a throwaway probe in the theme element's subtree and read the *computed* color,
|
|
13
|
+
// which the browser has already resolved to `rgb(…)`. Works for oklch, hsl-triplet, or hex tokens alike.
|
|
14
|
+
const readToken = (el, name, fallback) => {
|
|
15
|
+
const probe = document.createElement('span');
|
|
16
|
+
probe.style.cssText = `color: var(${name}); position: absolute; visibility: hidden; pointer-events: none`;
|
|
17
|
+
el.appendChild(probe);
|
|
18
|
+
const rgb = getComputedStyle(probe).color;
|
|
19
|
+
probe.remove();
|
|
20
|
+
// An unresolved var computes to the inherited color or fully-transparent black; treat that as "missing".
|
|
21
|
+
return rgb && rgb !== 'rgba(0, 0, 0, 0)' ? rgb : fallback;
|
|
22
|
+
};
|
|
23
|
+
// Element whose computed tokens reflect the theme: the viewer root when embedded, else <html>.
|
|
24
|
+
const probeElement = () => document.querySelector('.butin-viewer') ?? document.documentElement;
|
|
25
|
+
const computeTheme = () => {
|
|
26
|
+
const el = probeElement();
|
|
27
|
+
const muted = readToken(el, '--muted-foreground', '#888');
|
|
28
|
+
const border = readToken(el, '--border', '#2a2a35');
|
|
29
|
+
const popover = readToken(el, '--popover', '#1b1b22');
|
|
30
|
+
const popoverFg = readToken(el, '--popover-foreground', '#e8e8ea');
|
|
31
|
+
// Fallbacks for an unthemed embed; the live values come from the --chart-N tokens. Quiet-instrument:
|
|
32
|
+
// teal lead, then blue / amber / green / violet — no neon, no magenta.
|
|
33
|
+
const palette = ['#62d4c8', '#5b9bd5', '#d9a441', '#4bbf8a', '#9b7fd4'].map((fb, i) => readToken(el, `--chart-${i + 1}`, fb));
|
|
34
|
+
return {
|
|
35
|
+
LABEL: { color: muted, fontSize: 11 },
|
|
36
|
+
AXIS_LINE: {
|
|
37
|
+
axisLine: { lineStyle: { color: border } },
|
|
38
|
+
splitLine: { lineStyle: { color: border } }
|
|
39
|
+
},
|
|
40
|
+
TOOLTIP: { trigger: 'axis', backgroundColor: popover, borderColor: border, textStyle: { color: popoverFg } },
|
|
41
|
+
LEGEND_TEXT: muted,
|
|
42
|
+
ACCENT: palette[0],
|
|
43
|
+
PALETTE: palette
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
// Resolved ECharts styling for the current theme. Returns new objects whenever `.dark` toggles on
|
|
47
|
+
// <html>/<body>, so charts that fold these into a useMemo option builder re-color on the next render.
|
|
48
|
+
export const useEchartsTheme = () => {
|
|
49
|
+
const [tick, setTick] = useState(0);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const observer = new MutationObserver(() => setTick((t) => t + 1));
|
|
52
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
53
|
+
if (document.body) {
|
|
54
|
+
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
|
55
|
+
}
|
|
56
|
+
return () => observer.disconnect();
|
|
57
|
+
}, []);
|
|
58
|
+
return useMemo(() => computeTheme(), [tick]);
|
|
59
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const formatUsd: (n: number) => string;
|
|
2
|
+
export declare const formatMoney: (n: number, currency?: string, locale?: string) => string;
|
|
3
|
+
export declare const formatUsdCompact: (n: number) => string;
|
|
4
|
+
export declare const formatMoneyCompact: (n: number, currency?: string, locale?: string) => string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Format a number as USD (e.g. 1234.5 → "$1,234.50").
|
|
2
|
+
export const formatUsd = (n) => n.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
|
|
3
|
+
// Format a number in an explicit ISO-4217 currency (e.g. 1234.5, 'CAD' → "CA$1,234.50").
|
|
4
|
+
export const formatMoney = (n, currency = 'USD', locale = 'en-US') => n.toLocaleString(locale, { style: 'currency', currency });
|
|
5
|
+
// Compact USD for tight spaces: 2_230_000 → "$2.23M", 12_400 → "$12.4k", 52 → "$52". Keeps the sign.
|
|
6
|
+
export const formatUsdCompact = (n) => {
|
|
7
|
+
const abs = Math.abs(n);
|
|
8
|
+
const sign = n < 0 ? '-' : '';
|
|
9
|
+
if (abs >= 1_000_000) {
|
|
10
|
+
return `${sign}$${(abs / 1_000_000).toFixed(2)}M`;
|
|
11
|
+
}
|
|
12
|
+
if (abs >= 1_000) {
|
|
13
|
+
return `${sign}$${(abs / 1_000).toFixed(1)}k`;
|
|
14
|
+
}
|
|
15
|
+
return `${sign}$${Math.round(abs)}`;
|
|
16
|
+
};
|
|
17
|
+
// Compact money in an explicit currency via Intl compact notation: 2_230_000 CAD → "CA$2.2M", 12_400 → "$12K".
|
|
18
|
+
export const formatMoneyCompact = (n, currency = 'USD', locale = 'en-US') => n.toLocaleString(locale, { style: 'currency', currency, notation: 'compact', maximumFractionDigits: 1 });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface MonthBucket {
|
|
2
|
+
month: string;
|
|
3
|
+
amount: number;
|
|
4
|
+
}
|
|
5
|
+
export declare const monthRange: (start: string, end: string) => string[];
|
|
6
|
+
export declare const monthlyBuckets: (invoices: Array<{
|
|
7
|
+
date?: string;
|
|
8
|
+
amount: number;
|
|
9
|
+
}>, nowMonth: string) => MonthBucket[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Bucket invoices onto a continuous YYYY-MM axis from the earliest invoice through the latest (or the
|
|
2
|
+
// current month), gaps filled with 0 so a month with no spend stays an aligned slot instead of
|
|
3
|
+
// collapsing. Single series, no year picker. `nowMonth` is passed in to keep this pure/testable.
|
|
4
|
+
import { round2 } from '@butinapp/sdk/util';
|
|
5
|
+
import { groupBy, sumBy } from 'lodash-es';
|
|
6
|
+
import { DateTime } from 'luxon';
|
|
7
|
+
const monthOf = (date) => date.slice(0, 7);
|
|
8
|
+
export const monthRange = (start, end) => {
|
|
9
|
+
const out = [];
|
|
10
|
+
const last = DateTime.fromFormat(end, 'yyyy-MM', { zone: 'utc' });
|
|
11
|
+
for (let m = DateTime.fromFormat(start, 'yyyy-MM', { zone: 'utc' }); m <= last; m = m.plus({ months: 1 })) {
|
|
12
|
+
out.push(m.toFormat('yyyy-MM'));
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
16
|
+
export const monthlyBuckets = (invoices, nowMonth) => {
|
|
17
|
+
const dated = invoices.filter((inv) => typeof inv.date === 'string' && inv.date.length >= 7);
|
|
18
|
+
if (dated.length === 0) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const months = dated.map((inv) => monthOf(inv.date)).sort();
|
|
22
|
+
const start = months[0];
|
|
23
|
+
let end = months[months.length - 1];
|
|
24
|
+
if (nowMonth > end) {
|
|
25
|
+
end = nowMonth;
|
|
26
|
+
}
|
|
27
|
+
const byMonth = groupBy(dated, (inv) => monthOf(inv.date));
|
|
28
|
+
return monthRange(start, end).map((month) => ({
|
|
29
|
+
month,
|
|
30
|
+
amount: round2(sumBy(byMonth[month] ?? [], (inv) => inv.amount))
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { Button, buttonVariants } from './components/button.js';
|
|
2
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './components/card.js';
|
|
3
|
+
export { Input } from './components/input.js';
|
|
4
|
+
export { Badge, badgeVariants } from './components/badge.js';
|
|
5
|
+
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetTitle } from './components/sheet.js';
|
|
6
|
+
export { Dialog, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './components/dialog.js';
|
|
7
|
+
export { Skeleton } from './components/skeleton.js';
|
|
8
|
+
export { Separator } from './components/separator.js';
|
|
9
|
+
export { Label } from './components/label.js';
|
|
10
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './components/tooltip.js';
|
|
11
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './components/tabs.js';
|
|
12
|
+
export { Switch } from './components/switch.js';
|
|
13
|
+
export { Checkbox } from './components/checkbox.js';
|
|
14
|
+
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem } from './components/dropdown-menu.js';
|
|
15
|
+
export { Select } from './components/select.js';
|
|
16
|
+
export type { SelectOption } from './components/select.js';
|
|
17
|
+
export { Combobox } from './components/combobox.js';
|
|
18
|
+
export type { ComboboxProps } from './components/combobox.js';
|
|
19
|
+
export { ServiceIcon, ServiceIconProvider, readableOn } from './components/service-icon.js';
|
|
20
|
+
export type { ServiceIconResolver } from './components/service-icon.js';
|
|
21
|
+
export { DataTable } from './components/data-table.js';
|
|
22
|
+
export type { DataTableProps } from './components/data-table.js';
|
|
23
|
+
export type { DataTableColumn, DataTableState } from './components/data-table-model.js';
|
|
24
|
+
export { EChart } from './components/echart.js';
|
|
25
|
+
export { Sparkline } from './components/sparkline.js';
|
|
26
|
+
export { Progress } from './components/progress.js';
|
|
27
|
+
export { StatCard } from './features/charts.js';
|
|
28
|
+
export { cn } from './lib/utils.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @butinapp/ui/primitives — the design-system primitives: pure, prop-driven, theme-portable visual
|
|
2
|
+
// components with no app/IPC/Electron concept in them. The foundation every other layer composes.
|
|
3
|
+
export { Button, buttonVariants } from './components/button.js';
|
|
4
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './components/card.js';
|
|
5
|
+
export { Input } from './components/input.js';
|
|
6
|
+
export { Badge, badgeVariants } from './components/badge.js';
|
|
7
|
+
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetTitle } from './components/sheet.js';
|
|
8
|
+
export { Dialog, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './components/dialog.js';
|
|
9
|
+
export { Skeleton } from './components/skeleton.js';
|
|
10
|
+
export { Separator } from './components/separator.js';
|
|
11
|
+
export { Label } from './components/label.js';
|
|
12
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './components/tooltip.js';
|
|
13
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './components/tabs.js';
|
|
14
|
+
export { Switch } from './components/switch.js';
|
|
15
|
+
export { Checkbox } from './components/checkbox.js';
|
|
16
|
+
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem } from './components/dropdown-menu.js';
|
|
17
|
+
export { Select } from './components/select.js';
|
|
18
|
+
export { Combobox } from './components/combobox.js';
|
|
19
|
+
export { ServiceIcon, ServiceIconProvider, readableOn } from './components/service-icon.js';
|
|
20
|
+
export { DataTable } from './components/data-table.js';
|
|
21
|
+
export { EChart } from './components/echart.js';
|
|
22
|
+
export { Sparkline } from './components/sparkline.js';
|
|
23
|
+
export { Progress } from './components/progress.js';
|
|
24
|
+
export { StatCard } from './features/charts.js';
|
|
25
|
+
// The class-merge helper every layer builds on.
|
|
26
|
+
export { cn } from './lib/utils.js';
|
package/dist/shell.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { connDotClass, connLabel, ConnDot } from './features/shell/conn-state.js';
|
|
2
|
+
export type { ConnState } from './features/shell/conn-state.js';
|
|
3
|
+
export { AppShell } from './features/shell/app-shell.js';
|
|
4
|
+
export { ThemeToggle } from './features/shell/theme-toggle.js';
|
|
5
|
+
export { Sidebar } from './features/shell/sidebar.js';
|
|
6
|
+
export type { SidebarService, SidebarTarget, SidebarActive } from './features/shell/sidebar.js';
|
|
7
|
+
export { ServiceTabs } from './features/shell/service-tabs.js';
|
|
8
|
+
export type { ServiceTab } from './features/shell/service-tabs.js';
|
package/dist/shell.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @butinapp/ui/shell — the embeddable shell scaffolding: the structural, prop-driven pieces an embed composes
|
|
2
|
+
// into a dashboard (the app frame, the service sidebar, a service's capability tabs, the connection dot, the
|
|
3
|
+
// theme switch). NOT the Butin app's own chrome — profiles, vault, settings, notifications, and export
|
|
4
|
+
// are app-specific and live in core (core/src/renderer/chrome). The out-of-repo viewer builds its
|
|
5
|
+
// read-only dashboard from exactly this + /dashboard + /primitives.
|
|
6
|
+
export { connDotClass, connLabel, ConnDot } from './features/shell/conn-state.js';
|
|
7
|
+
export { AppShell } from './features/shell/app-shell.js';
|
|
8
|
+
export { ThemeToggle } from './features/shell/theme-toggle.js';
|
|
9
|
+
export { Sidebar } from './features/shell/sidebar.js';
|
|
10
|
+
export { ServiceTabs } from './features/shell/service-tabs.js';
|
package/dist/theme.css
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* The Butin design-system theme — the single source of truth for brand tokens.
|
|
2
|
+
|
|
3
|
+
A host's Tailwind build imports this right after `@import 'tailwindcss'`:
|
|
4
|
+
@import 'tailwindcss';
|
|
5
|
+
@import '@butinapp/ui/theme.css';
|
|
6
|
+
@source '<the host's component source>'; // host still scans the source it renders
|
|
7
|
+
|
|
8
|
+
It carries the semantic token palette (shadcn-named, dark-first), the `@theme inline` mapping that
|
|
9
|
+
turns those tokens into utilities, and the dark variant. This is SOURCE the host's single Tailwind
|
|
10
|
+
build consumes — @butinapp/ui still ships no compiled CSS (no preflight, no utilities), so there is no
|
|
11
|
+
second build to collide with. Importing the same tokens twice is idempotent.
|
|
12
|
+
|
|
13
|
+
Theme-portable by design: an embedder that wants its OWN palette skips this import and defines the same
|
|
14
|
+
semantic tokens itself; the components only ever reference the names. */
|
|
15
|
+
|
|
16
|
+
@custom-variant dark (&:is(.dark *));
|
|
17
|
+
|
|
18
|
+
:root {
|
|
19
|
+
color-scheme: light;
|
|
20
|
+
--radius: 0.5rem;
|
|
21
|
+
/* Surface elevation mirrors dark's stack (canvas → chrome → card), inverted: the canvas is a soft
|
|
22
|
+
cool-gray so white cards lift off it. A flat all-white surface reads as unfinished. */
|
|
23
|
+
--background: oklch(0.973 0.004 275);
|
|
24
|
+
--foreground: oklch(0.205 0.014 275);
|
|
25
|
+
--card: oklch(1 0 0);
|
|
26
|
+
--card-foreground: oklch(0.205 0.014 275);
|
|
27
|
+
--popover: oklch(1 0 0);
|
|
28
|
+
--popover-foreground: oklch(0.205 0.014 275);
|
|
29
|
+
--primary: oklch(0.58 0.09 188);
|
|
30
|
+
--primary-foreground: oklch(0.99 0.005 220);
|
|
31
|
+
--secondary: oklch(0.955 0.006 275);
|
|
32
|
+
--secondary-foreground: oklch(0.25 0.012 275);
|
|
33
|
+
--muted: oklch(0.955 0.006 275);
|
|
34
|
+
--muted-foreground: oklch(0.5 0.018 275);
|
|
35
|
+
--accent: oklch(0.94 0.008 275);
|
|
36
|
+
--accent-foreground: oklch(0.25 0.012 275);
|
|
37
|
+
--destructive: oklch(0.55 0.22 27);
|
|
38
|
+
--destructive-foreground: oklch(0.99 0.01 27);
|
|
39
|
+
--border: oklch(0.912 0.006 275);
|
|
40
|
+
--input: oklch(0.78 0.008 275);
|
|
41
|
+
--ring: oklch(0.58 0.09 188);
|
|
42
|
+
--chart-1: oklch(0.62 0.15 215);
|
|
43
|
+
--chart-2: oklch(0.55 0.1 230);
|
|
44
|
+
--chart-3: oklch(0.63 0.16 75);
|
|
45
|
+
--chart-4: oklch(0.6 0.16 162);
|
|
46
|
+
--chart-5: oklch(0.55 0.2 292);
|
|
47
|
+
--sidebar: oklch(0.965 0.005 275);
|
|
48
|
+
--sidebar-foreground: oklch(0.205 0.014 275);
|
|
49
|
+
--sidebar-primary: oklch(0.58 0.09 188);
|
|
50
|
+
--sidebar-primary-foreground: oklch(0.99 0.005 220);
|
|
51
|
+
--sidebar-accent: oklch(0.93 0.008 275);
|
|
52
|
+
--sidebar-accent-foreground: oklch(0.25 0.012 275);
|
|
53
|
+
--sidebar-border: oklch(0.912 0.006 275);
|
|
54
|
+
--sidebar-ring: oklch(0.58 0.09 188);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.dark {
|
|
58
|
+
color-scheme: dark;
|
|
59
|
+
--background: oklch(0.177 0.011 261);
|
|
60
|
+
--foreground: oklch(0.957 0.006 256);
|
|
61
|
+
--card: oklch(0.22 0.016 257);
|
|
62
|
+
--card-foreground: oklch(0.957 0.006 256);
|
|
63
|
+
--popover: oklch(0.258 0.02 255);
|
|
64
|
+
--popover-foreground: oklch(0.957 0.006 256);
|
|
65
|
+
--primary: oklch(0.801 0.105 186);
|
|
66
|
+
--primary-foreground: oklch(0.2 0.02 250);
|
|
67
|
+
--secondary: oklch(0.282 0.022 254);
|
|
68
|
+
--secondary-foreground: oklch(0.957 0.006 256);
|
|
69
|
+
--muted: oklch(0.282 0.022 254);
|
|
70
|
+
--muted-foreground: oklch(0.628 0.029 252);
|
|
71
|
+
--accent: oklch(0.3 0.018 255);
|
|
72
|
+
--accent-foreground: oklch(0.957 0.006 256);
|
|
73
|
+
--destructive: oklch(0.681 0.148 30);
|
|
74
|
+
--destructive-foreground: oklch(0.97 0.01 30);
|
|
75
|
+
--border: oklch(1 0 0 / 6%);
|
|
76
|
+
--input: oklch(1 0 0 / 12%);
|
|
77
|
+
--ring: oklch(0.801 0.105 186);
|
|
78
|
+
--chart-1: oklch(0.801 0.105 186);
|
|
79
|
+
--chart-2: oklch(0.62 0.1 230);
|
|
80
|
+
--chart-3: oklch(0.751 0.13 80);
|
|
81
|
+
--chart-4: oklch(0.725 0.131 161);
|
|
82
|
+
--chart-5: oklch(0.66 0.11 300);
|
|
83
|
+
--sidebar: oklch(0.195 0.012 259);
|
|
84
|
+
--sidebar-foreground: oklch(0.957 0.006 256);
|
|
85
|
+
--sidebar-primary: oklch(0.801 0.105 186);
|
|
86
|
+
--sidebar-primary-foreground: oklch(0.2 0.02 250);
|
|
87
|
+
--sidebar-accent: oklch(0.282 0.022 254);
|
|
88
|
+
--sidebar-accent-foreground: oklch(0.957 0.006 256);
|
|
89
|
+
--sidebar-border: oklch(1 0 0 / 6%);
|
|
90
|
+
--sidebar-ring: oklch(0.801 0.105 186);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@theme inline {
|
|
94
|
+
--color-background: var(--background);
|
|
95
|
+
--color-foreground: var(--foreground);
|
|
96
|
+
--color-card: var(--card);
|
|
97
|
+
--color-card-foreground: var(--card-foreground);
|
|
98
|
+
--color-popover: var(--popover);
|
|
99
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
100
|
+
--color-primary: var(--primary);
|
|
101
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
102
|
+
--color-secondary: var(--secondary);
|
|
103
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
104
|
+
--color-muted: var(--muted);
|
|
105
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
106
|
+
--color-accent: var(--accent);
|
|
107
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
108
|
+
--color-destructive: var(--destructive);
|
|
109
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
110
|
+
--color-border: var(--border);
|
|
111
|
+
--color-input: var(--input);
|
|
112
|
+
--color-ring: var(--ring);
|
|
113
|
+
--color-chart-1: var(--chart-1);
|
|
114
|
+
--color-chart-2: var(--chart-2);
|
|
115
|
+
--color-chart-3: var(--chart-3);
|
|
116
|
+
--color-chart-4: var(--chart-4);
|
|
117
|
+
--color-chart-5: var(--chart-5);
|
|
118
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
119
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
120
|
+
--radius-lg: var(--radius);
|
|
121
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
122
|
+
--color-sidebar: var(--sidebar);
|
|
123
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
124
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
125
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
126
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
127
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
128
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
129
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
130
|
+
|
|
131
|
+
/* Type system: geometric grotesk for the wordmark + headlines (font-display), monospace for all
|
|
132
|
+
data/status/numbers (font-mono — the "operator surface" signal), Inter/system for body. A host loads
|
|
133
|
+
the actual variable-font files; the stacks fall back gracefully when it doesn't. */
|
|
134
|
+
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
135
|
+
--font-display: 'Space Grotesk Variable', 'Space Grotesk', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
|
136
|
+
--font-mono: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Segoe UI Mono', monospace;
|
|
137
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { CapabilityView, ConfigFieldView, PluginView } from '@butinapp/shapes';
|
|
2
|
+
export type ReportResult = {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
error?: string;
|
|
6
|
+
lastRunAt?: string;
|
|
7
|
+
};
|
|
8
|
+
export type VerifyResult = {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// View-model props for the presentational components. The plugin view-model (PluginView · CapabilityView ·
|
|
2
|
+
// ConfigFieldView) is defined ONCE in @butinapp/shapes — the neutral package below both core and ui — and
|
|
3
|
+
// re-exported here so @butinapp/ui consumers keep one import site. core's IPC DTO (`PluginSummary`) is built
|
|
4
|
+
// as `PluginView & { …extras }`, so the app passes its DTOs straight through and an embed feeds the same
|
|
5
|
+
// components from a published snapshot. The two outcome shapes below stay here — they're ui-local.
|
|
6
|
+
export {};
|