@columenlabs/lumen-ui 0.1.236 → 0.1.238
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/assistant/settings.js +12 -14
- package/dist/assistant/usage-history.js +2 -1
- package/dist/assistant-screen.js +1 -1
- package/dist/people/lumen-people.js +8 -3
- package/dist/people/person-drawer.js +4 -2
- package/dist/people/shared.d.ts +3 -1
- package/dist/people/shared.js +2 -0
- package/dist/setting-parts.d.ts +4 -1
- package/dist/setting-parts.js +7 -1
- package/dist/settings.d.ts +6 -2
- package/dist/settings.js +9 -10
- package/package.json +2 -2
|
@@ -9,6 +9,7 @@ import { InfoLabel } from '../info-tip.js';
|
|
|
9
9
|
import { errorText } from '../integrations.js';
|
|
10
10
|
import { LumenSelector } from '../lumen-selector.js';
|
|
11
11
|
import { lumenToast } from '../lumen-toast.js';
|
|
12
|
+
import { SettingField } from '../setting-parts.js';
|
|
12
13
|
import { ShareMeter } from '../share-meter.js';
|
|
13
14
|
import { Badge } from '../ui/badge.js';
|
|
14
15
|
import { Button } from '../ui/button.js';
|
|
@@ -67,26 +68,23 @@ function WithStatus({ client, children, }) {
|
|
|
67
68
|
return _jsx(_Fragment, { children: children(q.data) });
|
|
68
69
|
}
|
|
69
70
|
/** One setting: its label (with an (i) when `info` says more) and its control. */
|
|
70
|
-
function Row({ label, info, children }) {
|
|
71
|
-
return (_jsxs("div", { "data-slot": "setting-row", className: "flex flex-wrap items-center gap-x-4 gap-y-2 border-b py-3 last:border-b-0", children: [_jsx(Label, { className: "min-w-0 flex-1", children: _jsx(InfoLabel, { info: info, children: label }) }), _jsx("div", { className: "shrink-0", children: children })] }));
|
|
72
|
-
}
|
|
73
71
|
// ---------------------------------------------------------------- the assistant
|
|
74
72
|
function General({ client }) {
|
|
75
73
|
const { apply } = useApply(client);
|
|
76
74
|
const set = (patch) => apply('settings', () => client.settings(patch));
|
|
77
75
|
return (_jsx(WithStatus, { client: client, children: (s) => {
|
|
78
76
|
const above = s.tiers.slice(s.tiers.findIndex((t) => t.id === s.tier) + 1);
|
|
79
|
-
return (_jsxs("div", { className: "flex flex-col", children: [_jsx(
|
|
77
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsx(SettingField, { label: "Tier", children: _jsx(LumenSelector, { label: "Tier", size: "sm", className: "w-44", value: s.tier, options: s.tiers.map((t) => ({
|
|
80
78
|
value: t.id,
|
|
81
79
|
label: t.label,
|
|
82
80
|
detail: t.model_label,
|
|
83
|
-
})), onChange: (tier) => void set({ tier }) }) }), s.tiers.length > 1 && (_jsx(
|
|
81
|
+
})), onChange: (tier) => void set({ tier }) }) }), s.tiers.length > 1 && (_jsx(SettingField, { label: "Hand off the work", info: "On its own, a higher tier takes one task in its own section of the chat and sends back a memo. It costs more.", children: _jsx(LumenSelector, { label: "Hand off the work", size: "sm", className: "w-44", value: s.hand_off, options: HAND_OFF, onChange: (hand_off) => void set({ hand_off }) }) })), s.tiers.length > 1 && (_jsx(SettingField, { label: "Ask for advice", info: "A bigger tier reads the chat and sends a plan before hard questions. It runs no tools, at most twice an answer.", children: _jsx(LumenSelector, { label: "Ask for advice", size: "sm", className: "w-44", value: s.advice, options: [
|
|
84
82
|
{ value: 'off', label: 'Off' },
|
|
85
83
|
...(above.length ? above : s.tiers).map((t) => ({
|
|
86
84
|
value: t.id,
|
|
87
85
|
label: t.label,
|
|
88
86
|
})),
|
|
89
|
-
], onChange: (advice) => void set({ advice }) }) })), _jsx(
|
|
87
|
+
], onChange: (advice) => void set({ advice }) }) })), _jsx(SettingField, { label: "Screen", info: "Takes you to a page, a Settings row, a chart or a card when you ask. Each move has an Undo; nothing is saved.", children: _jsx(LumenSelector, { label: "Screen", size: "sm", className: "w-44", value: s.screen ?? 'move', options: [
|
|
90
88
|
{ value: 'move', label: 'Move' },
|
|
91
89
|
{ value: 'off', label: 'Off' },
|
|
92
90
|
], onChange: (screen) => void set({ screen: screen }) }) })] }));
|
|
@@ -173,10 +171,10 @@ function Providers({ client }) {
|
|
|
173
171
|
// is exactly what the server may pay with.
|
|
174
172
|
const rows = s.providers.filter((p) => !p.own_ways || p.own_ways.length > 0);
|
|
175
173
|
const ownSet = rows.some(ownUsable);
|
|
176
|
-
return (_jsxs("div", { className: "flex flex-col", children: [_jsx(LumenExpandList, { children: rows.map((p) => (_jsx(OwnProviderKey, { client: client, p: p }, p.id))) }), ownSet && (_jsx(
|
|
174
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsx(LumenExpandList, { children: rows.map((p) => (_jsx(OwnProviderKey, { client: client, p: p }, p.id))) }), ownSet && (_jsx(SettingField, { label: "Answers run on", info: "Your own key or plan first, with the shared credits behind it; or the shared credits first.", children: _jsx(LumenSelector, { label: "Answers run on", size: "sm", className: "w-44", value: s.pay, options: [
|
|
177
175
|
{ value: 'own', label: 'Your own first' },
|
|
178
176
|
{ value: 'app', label: 'Shared credits first' },
|
|
179
|
-
], onChange: (pay) => void apply('pay', () => client.settings({ pay: pay })) }) })), ownSet && (_jsx(
|
|
177
|
+
], onChange: (pay) => void apply('pay', () => client.settings({ pay: pay })) }) })), ownSet && (_jsx(SettingField, { label: "Your keys may spend", info: "On your keys, every provider together.", children: _jsx(CapInput, { label: "Your monthly cap", value: s.caps.own, onSave: (n) => void apply('own', () => client.ownCap(n)) }) })), _jsx(OwnKeyUsage, { client: client, always: ownSet })] }));
|
|
180
178
|
} }));
|
|
181
179
|
}
|
|
182
180
|
/** One provider's shared key, which an admin sets for everyone. */
|
|
@@ -212,7 +210,7 @@ function SharedCap({ client, s }) {
|
|
|
212
210
|
const { apply } = useApply(client);
|
|
213
211
|
if (s.allowance?.period === 'once')
|
|
214
212
|
return null;
|
|
215
|
-
return (_jsx(
|
|
213
|
+
return (_jsx(SettingField, { label: "Each person may spend", info: "A month on the shared keys, every provider together. Past it, a person's own key pays.", children: _jsx(CapInput, { label: "Allowance per person", value: s.caps.app, onSave: (n) => void apply('cap', () => client.appCap(n)) }) }));
|
|
216
214
|
}
|
|
217
215
|
/** Admins only: the app's shared keys and what each person may spend on them. */
|
|
218
216
|
function SharedKeys({ client }) {
|
|
@@ -243,15 +241,15 @@ function Models({ client }) {
|
|
|
243
241
|
return (_jsxs("div", { className: "flex flex-col", children: [s.tiers.map((t) => {
|
|
244
242
|
const p = s.providers.find((x) => x.id === t.provider);
|
|
245
243
|
const keyless = p && !p.app.set && !p.app.from_env && !p.own.set && !p.plan?.set;
|
|
246
|
-
return (_jsx(
|
|
244
|
+
return (_jsx(SettingField, { label: t.label, info: t.about, children: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [keyless && _jsxs(Badge, { variant: "outline", children: ["No ", p.label, " key"] }), _jsx(LumenSelector, { label: `${t.label} model`, size: "sm", className: "w-36 sm:w-44", value: t.model, options: models, onChange: (model) => set({ tiers: { [t.id]: { model } } }) }), _jsx(LumenSelector, { label: `${t.label} effort`, size: "sm", className: "w-24 sm:w-32", value: t.effort, options: EFFORTS, onChange: (effort) => set({ tiers: { [t.id]: { effort } } }) })] }) }, t.id));
|
|
247
245
|
}), s.tasks.length > 1 &&
|
|
248
|
-
s.tasks.map((task) => (_jsx(
|
|
246
|
+
s.tasks.map((task) => (_jsx(SettingField, { label: `${task.label} starts on`, children: _jsx(LumenSelector, { label: `${task.label} starts on`, size: "sm", className: "w-44", value: task.tier, options: tiers, onChange: (tier) => set({ tasks: { [task.id]: tier } }) }) }, task.id)))] }));
|
|
249
247
|
} }));
|
|
250
248
|
}
|
|
251
249
|
// ---------------------------------------------------------------- tools
|
|
252
250
|
function Tools({ client }) {
|
|
253
251
|
const { apply } = useApply(client);
|
|
254
|
-
return (_jsx(WithStatus, { client: client, children: (s) => s.groups.length ? (_jsx("div", { className: "flex flex-col", children: s.groups.map((g) => (_jsx(
|
|
252
|
+
return (_jsx(WithStatus, { client: client, children: (s) => s.groups.length ? (_jsx("div", { className: "flex flex-col", children: s.groups.map((g) => (_jsx(SettingField, { label: g.label, info: g.about, children: _jsx(ToggleGroup, { type: "single", variant: "outline", size: "sm", spacing: 0, value: g.access, "aria-label": g.label, onValueChange: (v) => v &&
|
|
255
253
|
void apply('access', () => client.settings({ access: { [g.id]: v } })), children: ACCESS.filter((a) => (a.value !== 'draft' || g.drafts) && (a.value !== 'write' || g.writes)).map((a) => (_jsx(ToggleGroupItem, { value: a.value, children: a.label }, a.value))) }) }, g.id))) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: "This app gives the assistant no tools yet." })) }));
|
|
256
254
|
}
|
|
257
255
|
// ---------------------------------------------------------------- web
|
|
@@ -302,7 +300,7 @@ function ScrapeqlBlock({ client, s }) {
|
|
|
302
300
|
}
|
|
303
301
|
function Web({ client }) {
|
|
304
302
|
const { apply } = useApply(client);
|
|
305
|
-
return (_jsx(WithStatus, { client: client, children: (s) => (_jsxs("div", { className: "flex flex-col", children: [_jsx(
|
|
303
|
+
return (_jsx(WithStatus, { client: client, children: (s) => (_jsxs("div", { className: "flex flex-col", children: [_jsx(SettingField, { label: "Read the web", info: "A plain fetch of public pages and APIs from the server, never the app's own pages.", children: _jsx(Switch, { "aria-label": "Read the web", checked: s.web.fetch, onCheckedChange: (fetch) => void apply('web', () => client.settings({ web: { fetch } })) }) }), _jsx(ScrapeqlBlock, { client: client, s: s })] })) }));
|
|
306
304
|
}
|
|
307
305
|
// ---------------------------------------------------------------- spend
|
|
308
306
|
function Spend({ client }) {
|
|
@@ -346,7 +344,7 @@ function Allowance({ client }) {
|
|
|
346
344
|
: [{ where: 'What for', message: 'Say what it is for', target: whyId }]),
|
|
347
345
|
];
|
|
348
346
|
const ask = () => void apply('request', () => requestMore(Number(pick), why.trim()), 'Request sent').then((ok) => ok && setWhy(''));
|
|
349
|
-
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-col gap-2 border-b pb-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "font-semibold text-2xl tabular-nums", children: n(a.left) }), _jsxs("span", { className: "text-muted-foreground text-sm", children: ["left of ", n(a.total), a.period === 'month' ? ' this month' : ''] })] }), _jsx(ShareMeter, { value: a.total > 0 ? a.used / a.total : 1, wide: true })] }), r && (_jsx(
|
|
347
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-col gap-2 border-b pb-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "font-semibold text-2xl tabular-nums", children: n(a.left) }), _jsxs("span", { className: "text-muted-foreground text-sm", children: ["left of ", n(a.total), a.period === 'month' ? ' this month' : ''] })] }), _jsx(ShareMeter, { value: a.total > 0 ? a.used / a.total : 1, wide: true })] }), r && (_jsx(SettingField, { label: `Asked for ${n(r.amount)} · ${day(r.at)}`, info: r.why || undefined, children: _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [r.status === 'granted' && r.granted !== undefined && (_jsxs("span", { className: "tabular-nums", children: ["+", n(r.granted)] })), _jsx(RequestBadge, { r: r })] }) })), r?.status === 'open' ? (_jsx("p", { className: "pt-3 text-muted-foreground text-sm", children: "You can ask again once this one is decided." })) : (_jsxs("div", { className: "flex flex-col gap-2 pt-3", children: [_jsx(Input, { id: whyId, "aria-label": "What for", placeholder: "What it is for", className: "h-8 w-full", maxLength: 500, value: why, onChange: (e) => setWhy(e.target.value) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(LumenSelector, { label: "Amount", size: "sm", className: "w-40", value: pick, options: a.amounts.map((x) => ({ value: String(x), label: n(x) })), onChange: (v) => setAmount(v) }), _jsx(SaveButton, { issues: issues, label: "Request more", icon: SendIcon, busy: busy === 'request', onSave: ask })] })] }))] }));
|
|
350
348
|
} }));
|
|
351
349
|
}
|
|
352
350
|
/** One request, opened in place to grant (any amount) or decline. */
|
|
@@ -6,6 +6,7 @@ import { DetailsTable } from '../details-table.js';
|
|
|
6
6
|
import { IconButton } from '../icon-button.js';
|
|
7
7
|
import { InfoLabel } from '../info-tip.js';
|
|
8
8
|
import { cn } from '../lib/utils.js';
|
|
9
|
+
import { SettingField } from '../setting-parts.js';
|
|
9
10
|
import { Label } from '../ui/label.js';
|
|
10
11
|
import { Spinner } from '../ui/spinner.js';
|
|
11
12
|
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group.js';
|
|
@@ -119,7 +120,7 @@ export function OwnKeyUsage({ client, always }) {
|
|
|
119
120
|
.filter((e) => e.at >= since && !seen.has(e.id) && seen.add(e.id))
|
|
120
121
|
.sort((a, b) => b.at - a.at);
|
|
121
122
|
const total = sessions.reduce((a, e) => a + (e.own_usd ?? 0), 0);
|
|
122
|
-
return (_jsxs("div", {
|
|
123
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 border-t border-border/60 pb-3 first:border-t-0", children: [_jsx(SettingField, { label: "Spent on your keys", info: "Each chat and run your own key or plan paid for, by its last use.", className: "border-t-0", end: _jsx(ToggleGroup, { type: "single", variant: "outline", size: "sm", spacing: 0, value: range, "aria-label": "Range", onValueChange: (v) => v && setRange(v), children: OWN_RANGES.map((r) => (_jsx(ToggleGroupItem, { value: r.id, "aria-label": `Last ${r.id}`, children: r.id }, r.id))) }) }), sessions.length ? (_jsx(DetailsTable, { head: [
|
|
123
124
|
{ label: 'Session', grow: true },
|
|
124
125
|
{ label: 'Date', hideOnPhone: true },
|
|
125
126
|
{ label: 'Amount', right: true },
|
package/dist/assistant-screen.js
CHANGED
|
@@ -162,7 +162,7 @@ function ScreenProvider({ children }) {
|
|
|
162
162
|
return null;
|
|
163
163
|
const want = m.row.toLowerCase();
|
|
164
164
|
const row = await waitFor(() => [
|
|
165
|
-
...document.querySelectorAll('[data-slot="settings"] [data-slot="setting-
|
|
165
|
+
...document.querySelectorAll('[data-slot="settings"] [data-slot="setting-field"]'),
|
|
166
166
|
].find((el) => el.querySelector('label')?.textContent?.trim().toLowerCase().includes(want)));
|
|
167
167
|
if (!row)
|
|
168
168
|
return `no row "${m.row}" in that section`;
|
|
@@ -19,7 +19,7 @@ import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTi
|
|
|
19
19
|
import { InviteDrawer } from './invite-drawer.js';
|
|
20
20
|
import { PersonDrawer } from './person-drawer.js';
|
|
21
21
|
import { AuditSection, InvitesSection, PermissionsSection } from './sections.js';
|
|
22
|
-
import { LevelPicker, STATUS_LABEL, fromNow, peopleView, scopeKey, statusTone, topScopes, usePeopleAction, usePeopleQuery, useVisitView, when, withGrant, } from './shared.js';
|
|
22
|
+
import { LevelPicker, STATUS_LABEL, fromNow, peopleView, scopeKey, statusTone, syncText, topScopes, usePeopleAction, usePeopleQuery, useVisitView, when, withGrant, } from './shared.js';
|
|
23
23
|
export function LumenPeople({ adapter, crumbs = [], section: given, onSection, onSetUpMail, fields = [], }) {
|
|
24
24
|
const [own, setOwn] = useState('people');
|
|
25
25
|
const section = given ?? own;
|
|
@@ -167,8 +167,13 @@ function PeopleList({ frame, fields, onInvite, }) {
|
|
|
167
167
|
const invited = people.filter((p) => p.status === 'invited').length;
|
|
168
168
|
const active = people.filter((p) => p.status === 'active').length;
|
|
169
169
|
const disabled = people.filter((p) => p.status === 'disabled').length;
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
// The first-run note stands in for the table only when it has no row; a lone admin is a row.
|
|
171
|
+
const none = people.length === 0;
|
|
172
|
+
return (_jsxs(_Fragment, { children: [_jsx(LumenLayout, { crumbs: frame.crumbs, actions: frame.invite, intro: _jsxs("div", { className: "flex flex-col gap-3", children: [frame.tabs, _jsxs(TileRow, { fill: true, "aria-label": "People at a glance", children: [_jsx(Tile, { label: "People", value: people.length, caption: `${active} active${disabled ? ` · ${disabled} disabled` : ''}` }), _jsx(Tile, { label: "Admins", value: admins.length, caption: "Can\u2019t remove the last admin" }), _jsx(Tile, { label: "Invites pending", value: invited, caption: expired ? `${expired} expired` : 'none expired' }), view.sync ? (_jsx(Tile, { label: "Sign-in sync", value: syncText(view.sync), caption: !view.sync.configured
|
|
173
|
+
? 'Invites don’t reach sign-in'
|
|
174
|
+
: view.sync.at
|
|
175
|
+
? when(view.sync.at)
|
|
176
|
+
: view.sync.detail, valueClassName: !view.sync.configured || view.sync.ok ? undefined : 'text-loss' })) : (_jsx(Tile, { label: "Email", value: view.mail.configured ? 'Working' : 'Not set up', caption: view.mail.from ?? 'links only' }))] }), frame.mailNote, picked.size > 0 && (_jsx(BulkBar, { frame: frame, ids: [...picked], onClear: () => setPicked(new Set()) }))] }), domain: domain, ...layout, rows: people, load: load, state: none ? _jsx(FirstRun, { mail: view.mail.configured, onInvite: onInvite }) : undefined, selection: { keys: picked, onChange: setPicked }, rowTitle: (p) => p.name || p.email, onOpen: (p) => setOpenId(p.id) }), _jsx(LumenBoundary, { where: "people-person", what: "panel", children: _jsx(PersonDrawer, { adapter: adapter, view: view, person: open, onClose: () => setOpenId(null) }) })] }));
|
|
172
177
|
}
|
|
173
178
|
/** One change over the ticked people. Apply clears the selection; Undo puts the change back. */
|
|
174
179
|
function BulkBar({ frame, ids, onClear, }) {
|
|
@@ -14,7 +14,7 @@ import { LumenSelector } from '../lumen-selector.js';
|
|
|
14
14
|
import { NoteLine } from '../note-line.js';
|
|
15
15
|
import { Badge } from '../ui/badge.js';
|
|
16
16
|
import { Button } from '../ui/button.js';
|
|
17
|
-
import { LevelPicker, STATUS_LABEL, adminGuard, fromNow, topScopes, usePeopleAction, when, withGrant, } from './shared.js';
|
|
17
|
+
import { LevelPicker, STATUS_LABEL, adminGuard, fromNow, syncText, topScopes, usePeopleAction, when, withGrant, } from './shared.js';
|
|
18
18
|
const ROLES = [
|
|
19
19
|
{ value: 'member', label: 'Member', description: 'Only the levels set per scope' },
|
|
20
20
|
{ value: 'admin', label: 'Admin', description: 'Top level everywhere, and manages People' },
|
|
@@ -47,7 +47,9 @@ export function PersonDrawer({ adapter, view, person, onClose, }) {
|
|
|
47
47
|
},
|
|
48
48
|
view.sync && {
|
|
49
49
|
label: 'Sign-in provider',
|
|
50
|
-
value:
|
|
50
|
+
value: view.sync.at
|
|
51
|
+
? `${syncText(view.sync)} · ${when(view.sync.at)}`
|
|
52
|
+
: syncText(view.sync),
|
|
51
53
|
},
|
|
52
54
|
] }) }), adapter.audit && _jsx(ActivitySection, { adapter: adapter, person: p }), _jsx(DangerSection, { adapter: adapter, person: p, guard: guard, onGone: onClose })] })) }));
|
|
53
55
|
}
|
package/dist/people/shared.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Grant, type PeopleView, type Person, type PersonStatus, type Scope } from '@columenlabs/lumen-ui-types/people';
|
|
1
|
+
import { type Grant, type PeopleView, type Person, type PersonStatus, type Scope, type SyncStatus } from '@columenlabs/lumen-ui-types/people';
|
|
2
2
|
import { type CustomViewConfig } from '@columenlabs/lumen-ui-types/views';
|
|
3
3
|
import { type ReactNode } from 'react';
|
|
4
4
|
import type { Crumb } from '../page.js';
|
|
@@ -12,6 +12,8 @@ export declare const statusTone: (p: Pick<Person, 'status'>) => ViewChipTone | u
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function when(ts: number | null | undefined, now?: number): string;
|
|
14
14
|
/** `in 5 d`, `in 3 h`, `2 d ago`: how far a time is from now. */
|
|
15
|
+
/** A sign-in sync's one-word state: never set up is not a failed sync. */
|
|
16
|
+
export declare const syncText: (s: Pick<SyncStatus, 'configured' | 'ok'>) => "Not set up" | "Out of step" | "Synced";
|
|
15
17
|
export declare function fromNow(ts: number | null | undefined, now?: number): string;
|
|
16
18
|
/** The top scopes (apps), in order; constructs inside one have a `parent`. */
|
|
17
19
|
export declare const topScopes: (scopes: readonly Scope[]) => Scope[];
|
package/dist/people/shared.js
CHANGED
|
@@ -53,6 +53,8 @@ export function when(ts, now = Date.now()) {
|
|
|
53
53
|
}));
|
|
54
54
|
}
|
|
55
55
|
/** `in 5 d`, `in 3 h`, `2 d ago`: how far a time is from now. */
|
|
56
|
+
/** A sign-in sync's one-word state: never set up is not a failed sync. */
|
|
57
|
+
export const syncText = (s) => !s.configured ? 'Not set up' : s.ok ? 'Synced' : 'Out of step';
|
|
56
58
|
export function fromNow(ts, now = Date.now()) {
|
|
57
59
|
if (!ts)
|
|
58
60
|
return '—';
|
package/dist/setting-parts.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type ComponentType, type ReactNode } from 'react';
|
|
2
2
|
import { type KeyTestResult } from './integrations.js';
|
|
3
|
+
/** What Settings' search box holds: a `SettingField` whose label or info misses it hides. */
|
|
4
|
+
export declare const SettingsSearch: import("react").Context<string>;
|
|
5
|
+
export declare const settingMatches: (query: string, ...texts: ReactNode[]) => boolean;
|
|
3
6
|
/** One setting inside an item: its label, then its control group, then an optional switch at the end. */
|
|
4
7
|
export declare function SettingField({ label, info, htmlFor, end, stack, className, children, }: {
|
|
5
8
|
/** The setting's own name ("Keys", "Webhook"), never the thing's ("Plaid keys"). */
|
|
@@ -14,7 +17,7 @@ export declare function SettingField({ label, info, htmlFor, end, stack, classNa
|
|
|
14
17
|
className?: string;
|
|
15
18
|
/** The control: an input, a picker, a `SettingValue`, a ToggleGroup. Its own buttons sit inside it. */
|
|
16
19
|
children?: ReactNode;
|
|
17
|
-
}): import("react").JSX.Element;
|
|
20
|
+
}): import("react").JSX.Element | null;
|
|
18
21
|
/** A value read in place: cut to one line with the full text on hover, with Copy and Replace inside. */
|
|
19
22
|
export declare function SettingValue({ value, label, mono, copy, onReplace, replaceLabel, replaceIcon: ReplaceIcon, }: {
|
|
20
23
|
value: string;
|
package/dist/setting-parts.js
CHANGED
|
@@ -7,7 +7,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
// A row is never a lone button ("Last test", "Remove keys"), and a label never repeats the thing's name.
|
|
8
8
|
// Built on shadcn's Field and InputGroup parts.
|
|
9
9
|
import { AlertTriangleIcon, CheckIcon, CopyIcon, PencilIcon, RefreshCwIcon, Trash2Icon, } from 'lucide-react';
|
|
10
|
-
import { useState } from 'react';
|
|
10
|
+
import { createContext, useContext, useState } from 'react';
|
|
11
11
|
import { LumenConfirm } from './confirm.js';
|
|
12
12
|
import { IconButton } from './icon-button.js';
|
|
13
13
|
import { InfoTip } from './info-tip.js';
|
|
@@ -16,8 +16,14 @@ import { cn } from './lib/utils.js';
|
|
|
16
16
|
import { Badge } from './ui/badge.js';
|
|
17
17
|
import { InputGroup, InputGroupAddon, InputGroupButton } from './ui/input-group.js';
|
|
18
18
|
import { Label } from './ui/label.js';
|
|
19
|
+
/** What Settings' search box holds: a `SettingField` whose label or info misses it hides. */
|
|
20
|
+
export const SettingsSearch = createContext('');
|
|
21
|
+
export const settingMatches = (query, ...texts) => texts.some((t) => typeof t === 'string' && t.toLowerCase().includes(query.toLowerCase()));
|
|
19
22
|
/** One setting inside an item: its label, then its control group, then an optional switch at the end. */
|
|
20
23
|
export function SettingField({ label, info, htmlFor, end, stack = false, className, children, }) {
|
|
24
|
+
const query = useContext(SettingsSearch);
|
|
25
|
+
if (query && !settingMatches(query, label, info))
|
|
26
|
+
return null;
|
|
21
27
|
return (_jsxs("div", { "data-slot": "setting-field", "data-stack": stack || undefined, className: cn('flex min-h-10 min-w-0 items-center gap-3 border-t border-border/60 py-1.5 first:border-t-0', stack && 'max-sm:flex-wrap max-sm:gap-y-1.5', className), children: [_jsxs("div", { className: cn('flex w-32 shrink-0 items-center gap-1.5 max-sm:w-24', stack && 'max-sm:w-auto max-sm:flex-1',
|
|
22
28
|
// Nothing beside it but `end`: the label takes the row, so a short name never cuts off.
|
|
23
29
|
children === undefined && 'w-auto min-w-0 flex-1 shrink max-sm:w-auto'), children: [_jsx(Label, { htmlFor: htmlFor, className: "truncate font-normal text-muted-foreground", title: label, children: label }), info && _jsx(InfoTip, { label: `About ${label}`, children: info })] }), children !== undefined && (_jsx("div", { className: cn('flex min-w-0 max-w-md flex-1 items-center gap-2', stack && 'max-sm:order-2 max-sm:max-w-none max-sm:basis-full'), children: children })), end && _jsx("div", { className: cn('ml-auto shrink-0', stack && 'max-sm:order-1'), children: end })] }));
|
package/dist/settings.d.ts
CHANGED
|
@@ -31,13 +31,17 @@ export declare function SettingsActions({ children }: {
|
|
|
31
31
|
export declare function SettingsFooter({ children }: {
|
|
32
32
|
children: ReactNode;
|
|
33
33
|
}): import("react").ReactPortal | null;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* One setting: the kit's `SettingField` (docs/parts/settings.md), with `description` behind its (i).
|
|
36
|
+
* Kept so older call sites build; it draws nothing of its own. New code uses `SettingField`.
|
|
37
|
+
* @deprecated Use `SettingField`.
|
|
38
|
+
*/
|
|
35
39
|
export declare function SettingRow({ label, description, htmlFor, children, }: {
|
|
36
40
|
label: string;
|
|
37
41
|
description?: ReactNode;
|
|
38
42
|
htmlFor?: string;
|
|
39
43
|
children: ReactNode;
|
|
40
|
-
}): import("react").JSX.Element
|
|
44
|
+
}): import("react").JSX.Element;
|
|
41
45
|
/** Light, dark or system: in Settings with labels, in the profile menu as icons with tooltips. */
|
|
42
46
|
export declare function ThemeToggle({ compact }: {
|
|
43
47
|
compact?: boolean;
|
package/dist/settings.js
CHANGED
|
@@ -13,7 +13,7 @@ import { useAssistantMoves } from './assistant-screen.js';
|
|
|
13
13
|
import { InfoTip } from './info-tip.js';
|
|
14
14
|
import { LumenSelector } from './lumen-selector.js';
|
|
15
15
|
import { FONTS, FONT_SIZES, setPref, usePrefs, } from './prefs.js';
|
|
16
|
-
import { SettingStatus } from './setting-parts.js';
|
|
16
|
+
import { SettingField, SettingStatus, SettingsSearch, settingMatches as matches, } from './setting-parts.js';
|
|
17
17
|
import { TimeZoneSelect } from './status.js';
|
|
18
18
|
import { Dialog, DialogContent, DialogDescription, DialogTitle } from './ui/dialog.js';
|
|
19
19
|
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from './ui/input-group.js';
|
|
@@ -29,8 +29,6 @@ const THEMES = [
|
|
|
29
29
|
{ value: 'system', label: 'Follow the device', short: 'System', icon: MonitorIcon },
|
|
30
30
|
];
|
|
31
31
|
/** The Settings search text; rows that do not match it hide. */
|
|
32
|
-
const SearchContext = createContext('');
|
|
33
|
-
const matches = (query, ...texts) => texts.some((t) => typeof t === 'string' && t.toLowerCase().includes(query.toLowerCase()));
|
|
34
32
|
/** The open section's header slot for its actions (Add and Resync sit by the
|
|
35
33
|
* title, not on a sub-header). Each section gets its own, so search results keep theirs apart. */
|
|
36
34
|
const ActionsSlot = createContext(null);
|
|
@@ -51,12 +49,13 @@ function ActionsScope({ children, }) {
|
|
|
51
49
|
const [foot, setFoot] = useState(null);
|
|
52
50
|
return (_jsx(ActionsSlot.Provider, { value: el, children: _jsx(FooterSlot.Provider, { value: foot, children: children(setEl, setFoot) }) }));
|
|
53
51
|
}
|
|
54
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* One setting: the kit's `SettingField` (docs/parts/settings.md), with `description` behind its (i).
|
|
54
|
+
* Kept so older call sites build; it draws nothing of its own. New code uses `SettingField`.
|
|
55
|
+
* @deprecated Use `SettingField`.
|
|
56
|
+
*/
|
|
55
57
|
export function SettingRow({ label, description, htmlFor, children, }) {
|
|
56
|
-
|
|
57
|
-
if (query && !matches(query, label, description))
|
|
58
|
-
return null;
|
|
59
|
-
return (_jsxs("div", { "data-slot": "setting-row", className: "flex items-center gap-4 border-b py-3 last:border-b-0", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx(Label, { htmlFor: htmlFor, className: "truncate", children: label }), description && _jsx(InfoTip, { label: `About ${label}`, children: description })] }), _jsx("div", { className: "shrink-0", children: children })] }));
|
|
58
|
+
return (_jsx(SettingField, { label: label, info: description, htmlFor: htmlFor, children: children }));
|
|
60
59
|
}
|
|
61
60
|
/** Light, dark or system: in Settings with labels, in the profile menu as icons with tooltips. */
|
|
62
61
|
export function ThemeToggle({ compact = false }) {
|
|
@@ -148,7 +147,7 @@ export function LumenSettings({ open, section, onOpen, onClose, appLabel, profil
|
|
|
148
147
|
if (!q)
|
|
149
148
|
return;
|
|
150
149
|
const ids = [...(pane.current?.querySelectorAll('[data-settings-section]') ?? [])]
|
|
151
|
-
.filter((el) => el.dataset.whole === 'true' || el.querySelector('[data-slot=setting-
|
|
150
|
+
.filter((el) => el.dataset.whole === 'true' || el.querySelector('[data-slot=setting-field]'))
|
|
152
151
|
.map((el) => el.dataset.settingsSection ?? '');
|
|
153
152
|
setFound((f) => (f.join() === ids.join() ? f : ids));
|
|
154
153
|
});
|
|
@@ -201,6 +200,6 @@ export function LumenSettings({ open, section, onOpen, onClose, appLabel, profil
|
|
|
201
200
|
const whole = matches(q, s.title, s.description);
|
|
202
201
|
return (_jsx(ActionsScope, { children: (slot) => (_jsxs("div", { "data-settings-section": s.id, "data-whole": whole, "aria-labelledby": `settings-${s.id}-title`, className: whole
|
|
203
202
|
? 'flex flex-col gap-3'
|
|
204
|
-
: 'hidden flex-col gap-3 has-[[data-slot=setting-
|
|
203
|
+
: 'hidden flex-col gap-3 has-[[data-slot=setting-field]]:flex', children: [header(s, slot), _jsx(Separator, {}), _jsx(SettingsSearch.Provider, { value: whole ? '' : q, children: s.content })] })) }, s.id));
|
|
205
204
|
}), !found.length && (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["No settings match \u201C", q, "\u201D."] }))] })) : (_jsx(ActionsScope, { children: (slot, footer) => (_jsxs("section", { "aria-labelledby": `settings-${current.id}-title`, className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsxs("div", { className: "flex shrink-0 flex-col gap-3 px-4 pt-4 md:px-6 md:pt-6", children: [header(current, slot), _jsx(Separator, {})] }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-4 pt-3 pb-4 *:shrink-0 md:px-6 [&:has(+[data-slot=settings-footer]:empty)]:end-fade", children: current.content }), _jsx("div", { ref: footer, "data-slot": "settings-footer", className: "flex min-h-12 shrink-0 items-center justify-end gap-2 border-t px-4 py-2 empty:hidden md:px-6", children: current.footer })] })) }, current.id))] }) }));
|
|
206
205
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.238",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@codemirror/lang-javascript": "6.2.5",
|
|
50
50
|
"@codemirror/lang-json": "6.0.2",
|
|
51
51
|
"@codemirror/lang-sql": "6.10.0",
|
|
52
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.238",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|