@butinapp/ui 0.1.0 → 0.1.2
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/dist/butin.css +2310 -0
- package/dist/components/data-table.js +4 -2
- package/dist/features/shell/sidebar.d.ts +4 -1
- package/dist/features/shell/sidebar.js +3 -3
- package/dist/i18n/labels.d.ts +4 -0
- package/dist/i18n/labels.js +8 -0
- package/dist/lib/echarts-theme.js +3 -2
- package/dist/theme.css +16 -3
- package/package.json +11 -7
|
@@ -8,8 +8,9 @@ import { Checkbox } from './checkbox.js';
|
|
|
8
8
|
import { formatRows, normalizeTablePrefs, selection as sel, sortRows } from './data-table-model.js';
|
|
9
9
|
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './dropdown-menu.js';
|
|
10
10
|
import { Input } from './input.js';
|
|
11
|
-
// First entry is the default selection (see pageSize seeding below).
|
|
12
11
|
const DEFAULT_PAGE_SIZES = [15, 25, 50, 100];
|
|
12
|
+
// The page size a table opens at when nothing is saved (see pageSize seeding below).
|
|
13
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
13
14
|
// Generic, headless, theme-portable table. TanStack owns sorting/column-order/visibility/pagination;
|
|
14
15
|
// selection (external Set) and grouping (manual collapsible sections over the sorted rows) are layered
|
|
15
16
|
// on top so grouping stays fully controllable. Pure — prefs in via props, export out via callback.
|
|
@@ -45,7 +46,8 @@ export const DataTable = ({ columns, rows, getRowId, selection, onSelectionChang
|
|
|
45
46
|
sorting: (seed.sort ? [{ id: seed.sort.key, desc: seed.sort.dir === 'desc' }] : []),
|
|
46
47
|
columnOrder: (seed.columnOrder ?? []),
|
|
47
48
|
columnVisibility: (seed.columnVisibility ?? {}),
|
|
48
|
-
pageSize: seed.pageSize ??
|
|
49
|
+
pageSize: seed.pageSize ??
|
|
50
|
+
(pageSizes.includes(DEFAULT_PAGE_SIZE) ? DEFAULT_PAGE_SIZE : (pageSizes[0] ?? DEFAULT_PAGE_SIZE))
|
|
49
51
|
}), [seed, pageSizes]);
|
|
50
52
|
const [sorting, setSorting] = useState(seedStates.sorting);
|
|
51
53
|
const [columnOrder, setColumnOrder] = useState(seedStates.columnOrder);
|
|
@@ -11,6 +11,8 @@ export interface SidebarService {
|
|
|
11
11
|
}
|
|
12
12
|
export type SidebarTarget = {
|
|
13
13
|
kind: 'overview';
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'people';
|
|
14
16
|
} | {
|
|
15
17
|
kind: 'management';
|
|
16
18
|
} | {
|
|
@@ -20,12 +22,13 @@ export type SidebarTarget = {
|
|
|
20
22
|
serviceId: string;
|
|
21
23
|
};
|
|
22
24
|
export type SidebarActive = SidebarTarget;
|
|
23
|
-
export declare const Sidebar: ({ services, active, onSelect, onNavigate, onOpenSettings, showManagement, showDeveloper }: {
|
|
25
|
+
export declare const Sidebar: ({ services, active, onSelect, onNavigate, onOpenSettings, showManagement, showPeople, showDeveloper }: {
|
|
24
26
|
services: SidebarService[];
|
|
25
27
|
active: SidebarActive;
|
|
26
28
|
onSelect: (target: SidebarTarget) => void;
|
|
27
29
|
onNavigate?: () => void;
|
|
28
30
|
onOpenSettings?: () => void;
|
|
29
31
|
showManagement?: boolean;
|
|
32
|
+
showPeople?: boolean;
|
|
30
33
|
showDeveloper?: boolean;
|
|
31
34
|
}) => import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Database, LayoutGrid, Search, Settings, Wrench } from 'lucide-react';
|
|
2
|
+
import { Database, LayoutGrid, Search, Settings, Users, Wrench } from 'lucide-react';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useLabels } from '../../i18n/index.js';
|
|
5
5
|
import { cn, Input, ServiceIcon } from '../../primitives.js';
|
|
@@ -10,7 +10,7 @@ const rowClass = (active) => cn('flex w-full cursor-pointer items-center gap-2 r
|
|
|
10
10
|
// The fixed left rail: Management link, then a searchable service list with Overview pinned first. Each
|
|
11
11
|
// service shows its brand-color icon and a connection-confidence dot (when it has data). Pure +
|
|
12
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 }) => {
|
|
13
|
+
export const Sidebar = ({ services, active, onSelect, onNavigate, onOpenSettings, showManagement = true, showPeople = false, showDeveloper = true }) => {
|
|
14
14
|
const t = useLabels();
|
|
15
15
|
const [query, setQuery] = useState('');
|
|
16
16
|
const filtered = services.filter((s) => s.name.toLowerCase().includes(query.trim().toLowerCase()));
|
|
@@ -24,7 +24,7 @@ export const Sidebar = ({ services, active, onSelect, onNavigate, onOpenSettings
|
|
|
24
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
25
|
}
|
|
26
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: () => {
|
|
27
|
+
}))] }), showManagement || showPeople || 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, showPeople ? (_jsxs("button", { className: rowClass(active.kind === 'people'), onClick: () => go({ kind: 'people' }), children: [_jsx(Users, { className: "size-4 shrink-0" }), t.navPeople] })) : 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
28
|
onOpenSettings();
|
|
29
29
|
onNavigate?.();
|
|
30
30
|
}, children: [_jsx(Settings, { className: "size-4 shrink-0" }), t.navSettings] })) : null] })) : null] }));
|
package/dist/i18n/labels.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export interface ButinLabels {
|
|
|
11
11
|
navManagement: string;
|
|
12
12
|
navOverview: string;
|
|
13
13
|
navDeveloper: string;
|
|
14
|
+
navPeople: string;
|
|
15
|
+
peopleSummary: (people: number, services: number) => string;
|
|
16
|
+
peopleEmpty: string;
|
|
14
17
|
logsHeading: string;
|
|
15
18
|
navSettings: string;
|
|
16
19
|
devNavBrowser: string;
|
|
@@ -171,6 +174,7 @@ export interface ButinLabels {
|
|
|
171
174
|
refreshedAgo: (relative: string, absolute: string) => string;
|
|
172
175
|
refreshWarning: string;
|
|
173
176
|
connectPrompt: (service: string) => string;
|
|
177
|
+
disconnectedWithData: (service: string) => string;
|
|
174
178
|
noDataYet: string;
|
|
175
179
|
serviceCount: (n: number) => string;
|
|
176
180
|
overviewSpending: string;
|
package/dist/i18n/labels.js
CHANGED
|
@@ -9,6 +9,9 @@ export const en = {
|
|
|
9
9
|
navManagement: 'Management',
|
|
10
10
|
navOverview: 'Overview',
|
|
11
11
|
navDeveloper: 'Developer',
|
|
12
|
+
navPeople: 'People',
|
|
13
|
+
peopleSummary: (people, services) => `${people} ${people === 1 ? 'person' : 'people'} across ${services} ${services === 1 ? 'service' : 'services'}`,
|
|
14
|
+
peopleEmpty: 'No member data yet — services that report team members will show their people here after a refresh.',
|
|
12
15
|
logsHeading: 'Logs',
|
|
13
16
|
navSettings: 'Settings',
|
|
14
17
|
devNavBrowser: 'Navigation browser',
|
|
@@ -179,6 +182,7 @@ export const en = {
|
|
|
179
182
|
refreshedAgo: (relative, absolute) => `refreshed ${relative} · ${absolute}`,
|
|
180
183
|
refreshWarning: 'Refresh failed — showing the last loaded data.',
|
|
181
184
|
connectPrompt: (service) => `Connect ${service} to fetch its first report.`,
|
|
185
|
+
disconnectedWithData: (service) => `${service} is disconnected — showing the last loaded data.`,
|
|
182
186
|
noDataYet: 'No data yet',
|
|
183
187
|
serviceCount: (n) => `${n} ${n === 1 ? 'service' : 'services'}`,
|
|
184
188
|
overviewSpending: 'Spending',
|
|
@@ -422,6 +426,9 @@ export const fr = {
|
|
|
422
426
|
navManagement: 'Gestion',
|
|
423
427
|
navOverview: 'Aperçu',
|
|
424
428
|
navDeveloper: 'Développeur',
|
|
429
|
+
navPeople: 'Personnes',
|
|
430
|
+
peopleSummary: (people, services) => `${people} personne${people > 1 ? 's' : ''} sur ${services} service${services > 1 ? 's' : ''}`,
|
|
431
|
+
peopleEmpty: 'Aucune donnée de membres pour l’instant — les services qui rapportent des membres d’équipe apparaîtront ici après une actualisation.',
|
|
425
432
|
logsHeading: 'Journaux',
|
|
426
433
|
navSettings: 'Réglages',
|
|
427
434
|
devNavBrowser: 'Navigateur',
|
|
@@ -592,6 +599,7 @@ export const fr = {
|
|
|
592
599
|
refreshedAgo: (relative, absolute) => `actualisé ${relative} · ${absolute}`,
|
|
593
600
|
refreshWarning: 'Échec du rafraîchissement — affichage des dernières données chargées.',
|
|
594
601
|
connectPrompt: (service) => `Connectez ${service} pour récupérer ses premières données.`,
|
|
602
|
+
disconnectedWithData: (service) => `${service} est déconnecté — affichage des dernières données chargées.`,
|
|
595
603
|
noDataYet: 'Aucune donnée',
|
|
596
604
|
serviceCount: (n) => `${n} ${n === 1 ? 'service' : 'services'}`,
|
|
597
605
|
overviewSpending: 'Dépenses',
|
|
@@ -20,8 +20,9 @@ const readToken = (el, name, fallback) => {
|
|
|
20
20
|
// An unresolved var computes to the inherited color or fully-transparent black; treat that as "missing".
|
|
21
21
|
return rgb && rgb !== 'rgba(0, 0, 0, 0)' ? rgb : fallback;
|
|
22
22
|
};
|
|
23
|
-
// Element whose computed tokens reflect the theme: the
|
|
24
|
-
|
|
23
|
+
// Element whose computed tokens reflect the theme: the nearest `.butin` scope (an embed wrapper, or the app's
|
|
24
|
+
// <html>), which is where the token values are defined. Falls back to <html> if the class isn't found.
|
|
25
|
+
const probeElement = () => document.querySelector('.butin') ?? document.documentElement;
|
|
25
26
|
const computeTheme = () => {
|
|
26
27
|
const el = probeElement();
|
|
27
28
|
const muted = readToken(el, '--muted-foreground', '#888');
|
package/dist/theme.css
CHANGED
|
@@ -10,12 +10,21 @@
|
|
|
10
10
|
build consumes — @butinapp/ui still ships no compiled CSS (no preflight, no utilities), so there is no
|
|
11
11
|
second build to collide with. Importing the same tokens twice is idempotent.
|
|
12
12
|
|
|
13
|
+
SCOPED, not global: the concrete token VALUES live on the `.butin` scope class, never on the document
|
|
14
|
+
`:root`. That's the containment guarantee — a host embedding a Butin surface imports this file and its
|
|
15
|
+
own `:root` (and its own shadcn `--background` / `--chart-*` tokens) is never overwritten; Butin's values
|
|
16
|
+
apply only inside the `.butin` subtree. Every Butin root carries the class: the app's <html>, and each
|
|
17
|
+
embed wrapper (@butinapp/viewer's ButinViewer/ButinView). The @theme mapping stays global (it only
|
|
18
|
+
defines `--color-*` utility aliases as `var(--token)`, late-bound to whichever scope resolves them), so
|
|
19
|
+
Butin's `bg-background` inside `.butin` reads Butin's value and the host's utilities outside read the
|
|
20
|
+
host's.
|
|
21
|
+
|
|
13
22
|
Theme-portable by design: an embedder that wants its OWN palette skips this import and defines the same
|
|
14
|
-
|
|
23
|
+
shadcn-named tokens on `.butin` itself; the components only ever reference the names. */
|
|
15
24
|
|
|
16
25
|
@custom-variant dark (&:is(.dark *));
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
.butin {
|
|
19
28
|
color-scheme: light;
|
|
20
29
|
--radius: 0.5rem;
|
|
21
30
|
/* Surface elevation mirrors dark's stack (canvas → chrome → card), inverted: the canvas is a soft
|
|
@@ -54,7 +63,11 @@
|
|
|
54
63
|
--sidebar-ring: oklch(0.58 0.09 188);
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
.dark
|
|
66
|
+
/* Dark applies when the scope element itself is dark (`.butin.dark`, the standalone viewer's own toggle) or
|
|
67
|
+
sits under a dark ancestor (`.dark .butin`, a host that owns the theme). Both resolve the dark values only
|
|
68
|
+
within the Butin subtree. */
|
|
69
|
+
.butin.dark,
|
|
70
|
+
.dark .butin {
|
|
58
71
|
color-scheme: dark;
|
|
59
72
|
--background: oklch(0.177 0.011 261);
|
|
60
73
|
--foreground: oklch(0.957 0.006 256);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@butinapp/ui",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Butin's embeddable, theme-portable design system + the generic data-view renderer (DashboardRenderer, Overview, app shell). Pure, prop-driven, no Electron. Ships
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Butin's embeddable, theme-portable design system + the generic data-view renderer (DashboardRenderer, Overview, app shell). Pure, prop-driven, no Electron. Ships a self-contained, .butin-scoped butin.css for drop-in embeds (zero host build), plus theme.css for a host that co-builds.",
|
|
5
5
|
"homepage": "https://butin.app",
|
|
6
6
|
"bugs": "https://github.com/butinapp/butin/issues",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"types": "./dist/i18n/index.d.ts",
|
|
38
38
|
"import": "./dist/i18n/index.js"
|
|
39
39
|
},
|
|
40
|
-
"./theme.css": "./dist/theme.css"
|
|
40
|
+
"./theme.css": "./dist/theme.css",
|
|
41
|
+
"./butin.css": "./dist/butin.css"
|
|
41
42
|
},
|
|
42
43
|
"publishConfig": {
|
|
43
44
|
"access": "public"
|
|
@@ -52,10 +53,11 @@
|
|
|
52
53
|
"luxon": "^3.7.2",
|
|
53
54
|
"radix-ui": "^1.5.0",
|
|
54
55
|
"tailwind-merge": "^3.6.0",
|
|
55
|
-
"@butinapp/
|
|
56
|
-
"@butinapp/
|
|
56
|
+
"@butinapp/sdk": "0.1.2",
|
|
57
|
+
"@butinapp/shapes": "0.1.2"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
60
|
+
"@tailwindcss/postcss": "^4.3.1",
|
|
59
61
|
"@testing-library/jest-dom": "^6.9.1",
|
|
60
62
|
"@testing-library/react": "^16.3.2",
|
|
61
63
|
"@testing-library/user-event": "^14.6.1",
|
|
@@ -63,14 +65,16 @@
|
|
|
63
65
|
"@types/luxon": "^3.7.1",
|
|
64
66
|
"@types/react": "^19.2.17",
|
|
65
67
|
"@types/react-dom": "^19.2.3",
|
|
66
|
-
"jsdom": "^29.1.1"
|
|
68
|
+
"jsdom": "^29.1.1",
|
|
69
|
+
"postcss": "^8.5.15",
|
|
70
|
+
"tailwindcss": "^4.3.1"
|
|
67
71
|
},
|
|
68
72
|
"peerDependencies": {
|
|
69
73
|
"react": "^19.2.7",
|
|
70
74
|
"react-dom": "^19.2.7"
|
|
71
75
|
},
|
|
72
76
|
"scripts": {
|
|
73
|
-
"build": "tsgo -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
77
|
+
"build": "tsgo -p tsconfig.build.json && node scripts/copy-assets.mjs && node scripts/build-css.mjs",
|
|
74
78
|
"typecheck": "tsgo --noEmit -p tsconfig.json",
|
|
75
79
|
"test": "vitest run"
|
|
76
80
|
},
|