@columenlabs/lumen-ui 0.1.235 → 0.1.237
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.
|
@@ -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 }) }) })] }));
|
|
@@ -117,12 +115,20 @@ const wayTest = (k, plan = false) => k?.test
|
|
|
117
115
|
: null;
|
|
118
116
|
/** A provider's mark: its initial on a tile. */
|
|
119
117
|
const mark = (label) => _jsx(ConnectionLogo, { text: label.charAt(0) });
|
|
118
|
+
/**
|
|
119
|
+
* Whether the app lets this person use `w` of their own on `p` (the app's `ownWays`, the same
|
|
120
|
+
* rule the server pays by). A way it does not allow never shows, even when one is still stored:
|
|
121
|
+
* the server would not run on it, so Settings never says it works.
|
|
122
|
+
*/
|
|
123
|
+
const mayOwn = (p, w) => !p.own_ways || p.own_ways.includes(w);
|
|
124
|
+
/** Whether the person has a way of their own on `p` that the app lets them use. */
|
|
125
|
+
const ownUsable = (p) => (mayOwn(p, 'key') && p.own.set) || (mayOwn(p, 'plan') && !!p.plan?.set);
|
|
120
126
|
/** One provider, collapsed to a row: the person's own key and, where it has one, their plan sign-in. */
|
|
121
127
|
function OwnProviderKey({ client, p }) {
|
|
122
128
|
const { apply, refresh } = useApply(client);
|
|
123
129
|
const ways = [];
|
|
124
|
-
const may = (w) =>
|
|
125
|
-
if (p.signin === 'chatgpt' &&
|
|
130
|
+
const may = (w) => mayOwn(p, w);
|
|
131
|
+
if (p.signin === 'chatgpt' && may('plan'))
|
|
126
132
|
ways.push({
|
|
127
133
|
id: 'plan',
|
|
128
134
|
label: 'ChatGPT plan',
|
|
@@ -139,7 +145,7 @@ function OwnProviderKey({ client, p }) {
|
|
|
139
145
|
removeText: 'Your API key, if you set one, runs from then on.',
|
|
140
146
|
removeLabel: 'Sign out',
|
|
141
147
|
});
|
|
142
|
-
if (may('key')
|
|
148
|
+
if (may('key'))
|
|
143
149
|
ways.push({
|
|
144
150
|
id: 'key',
|
|
145
151
|
label: 'API key',
|
|
@@ -161,14 +167,14 @@ function OwnProviderKey({ client, p }) {
|
|
|
161
167
|
function Providers({ client }) {
|
|
162
168
|
const { apply } = useApply(client);
|
|
163
169
|
return (_jsx(WithStatus, { client: client, children: (s) => {
|
|
164
|
-
// A provider the app gives this person no way of their own on shows no row
|
|
165
|
-
//
|
|
166
|
-
const rows = s.providers.filter((p) => !p.own_ways || p.own_ways.length > 0
|
|
167
|
-
const ownSet = rows.some(
|
|
168
|
-
return (_jsxs("div", { className: "flex flex-col", children: [_jsx(LumenExpandList, { children: rows.map((p) => (_jsx(OwnProviderKey, { client: client, p: p }, p.id))) }), ownSet && (_jsx(
|
|
170
|
+
// A provider the app gives this person no way of their own on shows no row: what shows here
|
|
171
|
+
// is exactly what the server may pay with.
|
|
172
|
+
const rows = s.providers.filter((p) => !p.own_ways || p.own_ways.length > 0);
|
|
173
|
+
const ownSet = rows.some(ownUsable);
|
|
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: [
|
|
169
175
|
{ value: 'own', label: 'Your own first' },
|
|
170
176
|
{ value: 'app', label: 'Shared credits first' },
|
|
171
|
-
], 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 })] }));
|
|
172
178
|
} }));
|
|
173
179
|
}
|
|
174
180
|
/** One provider's shared key, which an admin sets for everyone. */
|
|
@@ -204,7 +210,7 @@ function SharedCap({ client, s }) {
|
|
|
204
210
|
const { apply } = useApply(client);
|
|
205
211
|
if (s.allowance?.period === 'once')
|
|
206
212
|
return null;
|
|
207
|
-
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)) }) }));
|
|
208
214
|
}
|
|
209
215
|
/** Admins only: the app's shared keys and what each person may spend on them. */
|
|
210
216
|
function SharedKeys({ client }) {
|
|
@@ -235,15 +241,15 @@ function Models({ client }) {
|
|
|
235
241
|
return (_jsxs("div", { className: "flex flex-col", children: [s.tiers.map((t) => {
|
|
236
242
|
const p = s.providers.find((x) => x.id === t.provider);
|
|
237
243
|
const keyless = p && !p.app.set && !p.app.from_env && !p.own.set && !p.plan?.set;
|
|
238
|
-
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));
|
|
239
245
|
}), s.tasks.length > 1 &&
|
|
240
|
-
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)))] }));
|
|
241
247
|
} }));
|
|
242
248
|
}
|
|
243
249
|
// ---------------------------------------------------------------- tools
|
|
244
250
|
function Tools({ client }) {
|
|
245
251
|
const { apply } = useApply(client);
|
|
246
|
-
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 &&
|
|
247
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." })) }));
|
|
248
254
|
}
|
|
249
255
|
// ---------------------------------------------------------------- web
|
|
@@ -294,7 +300,7 @@ function ScrapeqlBlock({ client, s }) {
|
|
|
294
300
|
}
|
|
295
301
|
function Web({ client }) {
|
|
296
302
|
const { apply } = useApply(client);
|
|
297
|
-
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 })] })) }));
|
|
298
304
|
}
|
|
299
305
|
// ---------------------------------------------------------------- spend
|
|
300
306
|
function Spend({ client }) {
|
|
@@ -338,7 +344,7 @@ function Allowance({ client }) {
|
|
|
338
344
|
: [{ where: 'What for', message: 'Say what it is for', target: whyId }]),
|
|
339
345
|
];
|
|
340
346
|
const ask = () => void apply('request', () => requestMore(Number(pick), why.trim()), 'Request sent').then((ok) => ok && setWhy(''));
|
|
341
|
-
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 })] })] }))] }));
|
|
342
348
|
} }));
|
|
343
349
|
}
|
|
344
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`;
|
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.237",
|
|
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.237",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|